I trying to call a a function inside a array. I get an error message : The expression type { function } is not assignable to boolean.
Declaration:
public conditions: { (value: any): bool; }[] = [];
Usage:
var myBool: bool = conditions["myKey"]("someParam");
Whats wrong here ? Do i need to cast here ?
This works in the TypeScript playground, which uses 0.8.3. Are you sure this is actually the code in question? Indexing an array by a string produces a value of type any, so the function invocation there is sort of irrelevant for type purposes since it will just produce an any as well.
Related
I am trying to create a relatively simple function signature like the following:
function<U>(...args:U[]):U {/* body */}
I want typescript to combine the types of the args passed into a union instead of simply inferring U as the type of the first argument. Please how would I go about this?
The problem you're having is that the following code produces a compiler error:
function f<U>(...args: U[]): U {
return args[Math.floor(Math.random() * args.length)]
}
const a = f("string", 123, true); // error!
// ~~~ <-- error! 123 is not a string
// U inferred as string
// const a: string
Compiler heuristics prevent inferring a union here because often people want that error to happen. See Why isn't the type argument inferred as a union type? for more information.
There is an open feature request at microsoft/TypeScript#44312 asking for some way to tell the compiler that you want a union instead of an error. For now this is not possible, so if you want similar behavior you need to use workarounds.
One common workaround is to change the function so that it is generic not in the type U of the elements of args, but in the array type T of args itself. From there, you can index into that type with number to get the element type. That is, if T is an arraylike type, then T[number] is the element type (since that's the type you get when you index into the array with a number):
function f<T extends any[]>(...args: T): T[number] {
return args[Math.floor(Math.random() * args.length)]
}
const a = f("string", 123, true);
// T inferred as [string, number, boolean]
// const a: string | number | boolean
Now there's no error, and the type of a is the union you wanted.
Playground link to code
type transaction = {
uid: string,
paymentMode : string,
}
I want to get key name from type for e.g.
function getFieldName(input) {
returns a string with key name
}
const tran : transaction = {}
getKeyName(tran.paymentMode) returns 'paymentMode'
I read many articles and tried some solutions, so I understand we will at least need to create object for that type which is fine.
I want to do this because key name of a type is required and I also want to keep auto-complete for key name when we type something like obj.key to avoid mistakes.
I know, I can create a separate object from type with constant key, value pair. I want to avoid this, because then we will need to change same thing at 2 places whenever there is a change in type, which can be missed.
Edit:
I was looking for exactly this - nameof in c#. #Aleksey L. told this in comments.
This seems to be the type of function signature that you want:
Note that it meets your criteria of compatibility with IntelliSense auto-complete/suggest:
TS Playground
type Transaction = {
uid: string;
paymentMode: string;
};
function getFieldName <T extends object, K extends keyof T>(o: T, key: K): K {
return key;
}
const transaction: Transaction = {
uid: window.crypto.randomUUID(),
paymentMode: 'unknown',
};
getFieldName(transaction, 'uid'); // "uid"
getFieldName(transaction, 'paymentMode'); // "paymentMode"
getFieldName(transaction, 'asdf'); /*
~~~~~~
Argument of type '"asdf"' is not assignable to parameter of type 'keyof Transaction'.(2345) */
It is not possible to derive a string property name in JavaScript (or TypeScript) by passing a reference to the property value. JavaScript simply doesn't have this kind of meta-introspection capability at present.
It appears that your question is not just about type safety, but about DX/ergonomics. The above solution does require providing the property name, but the number of extra characters to type before getting the IntelliSense prompt is only 1 (2 if you consider whitespace):
// For: obj.prop:
// desired:
fn(obj.p_)
// 012^
// proposed:
fn(obj, 'p_')
// 01234^
// The closing quote is auto-inserted by IntelliSense, just like the closing parenthesis.
Take the below for instance. I'm not quite sure what the error message means, but it seems that logically, the signature is completely valid. Is this just not supported by TS?
function _createNominalCollection<isOutputOrdered_T extends boolean>(
input: nominal_T,
processingFunc: (count: number) => number,
orderedOutput: isOutputOrdered_T = true,
)
^^^
Type 'boolean' is not assignable to type 'isOutputOrdered_T'.
'boolean' is assignable to the constraint of type 'isOutputOrdered_T', but 'isOutputOrdered_T' could be instantiated with a different subtype of constraint 'boolean'.ts(2322)
As #VLAZ pointed out _createNominalCollection<false>() is problematic. Let's look at this error again:
'boolean' is assignable to the constraint of type 'isOutputOrdered_T', but 'isOutputOrdered_T' could be instantiated with a different subtype of constraint 'boolean'.ts(2322)
What that means is that you pass an explicit <false> type as the generic parameter, isOutputOrdered_T is now constrained to false but then the default argument is true, which would violate that.
Or to put it another way, true and false are subtypes of boolean, and your function allows the boolean to be constrained to one of those subtypes, but doesn't guarantee assignments to that variable will all be of that same subtype.
Let me propose an alternative.
When you have a function that returns different types based on different arguments, you should always consider if function overloads are better suited to model that instead of generics. They allow you to specifically map argument patterns to specific return type in a simple way without any generics at all.
For example:
// sorted version
function myFn(orderedOutput?: true): 'sorted'
// unsorted version
function myFn(orderedOutput: false): 'unsorted'
// Implementation
function myFn(orderedOutput: boolean = true): 'sorted' | 'unsorted' {
return orderedOutput ? 'sorted' : 'unsorted'
}
// Testing
const a = myFn(true) // sorted
const b = myFn(false) // unsorted
const c = myFn() // sorted
Here you create two signatures for your function. The first "sorted" version accepts no argument or true. The second "unsorted" version accepts false. Then you have an implementation that handle both cases.
Playground
The reason you get this error, is because isOutputOrdered_T could be a boolean subtype like type truthy = true or type falsy = false instead of being a complete boolean like type bool = true | false. For example:
function _createNominalCollection<T extends boolean>(
orderedOutput: T
) {}
type booleanSubtype = true;
_createNominalCollection<booleanSubtype>(false);
In this example the compiler would complain about passing false, because booleanSubtype only allows true as input:
Argument of type 'false' is not assignable to parameter of type 'true'
In case of a truthy boolean subtype, like in the example, your default value wouldn't be a valid input and thats why the compiler is warning you. Your example could work without compiler warning if you typecast your default value like this:
function _createNominalCollection<T extends boolean>(
orderedOutput: T = false as T
) {}
But as shown before, this wouldn't be actually correct with a truthy subtype, just getting rid of the error.
You have option to not assign default to false - leave it as undefined.
function f<T extends boolean=false>(a?: T) {
return a ? 'isTrue' : 'isNotTrue'
}
Thought I'd question why do you need this generic in first place. Does return type change depending on this flag? If it does change why does it need to be single function, would it not make more sense to have 2 distinctly named functions?
You get the error because you assign true to orderedOutput as a default parameter.
If you would call _createNominalCollection(..,..,false) the constrainted type parameter isOutputOrdered_T would be inferred to false. Of course it is not allowed to assign the value true to a parameter of type false.
I have a function that deals with two type of parameters: string and object. There are 3 different object structure expected. That makes up to 4 possibles types:
type URL = string;
type Item = {| href: string |};
type ItemList = {| results: Item[] |};
type Params = {| offset?: number, limit?: number |};
So the function's options type is:
type Options = URL | Item | ItemList | Params;
And here's the actual function:
// No properties of "Params" are required
// so if they're all omitted, Params === {}
function request(opts: Options = {}) {
if (typeof opts === 'string') {
return 'opts is an URL';
}
if (typeof opts.href === 'string') {
return 'opts is an item';
}
if (Array.isArray(opts.results)) {
return 'opts is a list of items';
}
// Three of the four types are caught
// we're left with "Params" which may or may not
// have a "offset" and "limit" property.
// Destructuring to undefined values is fine here.
// Still, flow complains about the type not being met.
const { offset, limit } = opts;
return 'opts are parameters';
}
Flow is complaining about a few things:
opts = {} throws an incompatibility error. Since no properties of Params are required, shouldn't empty objects match it too? Note that opts = { offset: undefined } clears the error.
Properties "offset" and "limit" are declared not found. Since none of them are required, shouldn't undefined be valid values? And thus destructuring fine?
To summarize my question:
How do you define a type that accepts different types of object, with one having no required properties?
Edit: run the flow code in your browser.
Check out Flowtype - making a sealed empty object for an answer to your first question.
For your second, the answer basically is that Flow does not fully support this sort of type refinement. Disjoint unions were designed for this use case, though you do have to add a discriminator property to all of your objects. Obviously, this will require some nontrivial changes to your code. It's up to you to decide if this is feasible.
If it's not feasible, the best thing to do is probably to just cast through any in this function, and make sure you provide a type annotation for the return value. It looks like it's small enough that it's easy for a human to reason about, so the benefit of typechecking here may not be worth the effort. Of course that's a judgement call that is best left to you.
Suppose I have a function that needs to return something of type StringMap<string, boolean>. An example return that is valid is: {"required": true}.
Now, I've read in a tutorial (it's not important which tutorial) you can create a function that has return type of { [s: string]: boolean } and this is the same return type as the StringMap above.
I don't understand how are these two the same return type? And how the second version is even valid?
All the return types I have seen in TypeScript have only included the type in the past i.e. boolean, number, any. For example function (): number {}. In our second version we use s: string which means we give the variable a name, and specify it's type, how are we suddenly allowed to give the variable the name s?
On top of that we put this string inside an array [s: string] as the key in the second version (therefore the key is now an array). While a StringMap has a string as the key.
The syntax is a bit different than you think. It's a unique syntax for defining dictionaries\maps.
{ [s: string]: boolean } means: a map, which has a key with type string, and it's values are boolean. The s means nothing at all, it could have been anything you want.
(Then why give it a name in the first place? my guess is to make the code more clear, when mapping more complex types. Sometimes you'll want to call the index id, sometimes address, etc..)
More info here, indexed types is what you want.
The Typescript handbook online isn't the most friendly documentation ever, but I think it's good enough and I recommend everyone who uses typescript to at least skim through it. Especially since in 2.0+ they added a bunch of crazy\awesome type features like mapped types.
The type { [s: string]: boolean } defines an indexable type interface.
What you see as an array is just the syntax decided to define the index of the interface.
The name of the key, as far as I know, is ignored and only the type is what matters.
This code { [s: string]: boolean } is defining an indexable interface where the indices are strings and the values are booleans.
I assume that the definition of StringMap is as follows:
export interface StringMap<T, U> = { [s: T]: U };
Which is kind of redundant if you ask me (as the name says that it should be a string map, so the keys should be strings). I would have declared the IStringMap interface as:
export interface IStringMap<T> = { [key: string]: T };
Interfaces in TypeScript just define the "shape" of the object. The previous three definitions have equivalent shapes, so this is perfectly valid:
function fn() : IStringMap<boolean> {
let myMap : StringMap<string, bool> = { };
myMap["foo"] = true;
myMap["bar"] = false;
myMap["baz"] = true;
return myMap;
}
let foo: { [bazzinga: string]: boolean } = fn();