Is there a type in TypeScript for anything except functions?
I would like to express that a paremeter should be an object or a simple value type (number, bool, string, etc.), but not a function.
If I use Object
, the compiler let's me to assign a function.
var test: Object = () => "a";
If I use any
, the same result of course too. Is there a type or trick which can help me out in this case?
My underlying goal is to garantee safety when using Knockout observables, so that I don't forget those little paranthesis to unwrap them :)
Solution 1:
New Feature Answer
Added November 2018 - as conditional types are a thing now!
Conditional types provide a possible solution to this, as you can create a NotFunction
conditional type, as shown below:
type NotFunction<T> = T extends Function ? never : T;
This works as follows:
const aFunction = (input: string) => input;
const anObject = { data: 'some data' };
const aString = 'data';
// Error function is not a never
const x: NotFunction<typeof aFunction> = aFunction;
// OK
const y: NotFunction<typeof anObject> = anObject;
const z: NotFunction<typeof aString> = aString;
The only weakness in this is that you have to put the variable on the left side and right side of the statement - although there is safety if you make a mistake such as:
// Error - function is not a string
const x: NotFunction<typeof aString> = aFunction;
Original Answer
You can provide a runtime check using typeof
, which although isn't a compile time check will catch those instances where you forget to execute the function:
function example(input: any) {
if (typeof input === 'function') {
alert('You passed a function!');
}
}
function someFunction() {
return 1;
}
// Okay
example({ name: 'Zoltán' });
example(1);
example('a string');
example(someFunction());
// Not okay
example(function () {});
example(someFunction);
Why can't you actually do what you want?
You almost can, because you could use an overload to allow "one of many types", for example:
class Example {
someMethod(input: number);
someMethod(input: string);
someMethod(input: boolean);
someMethod(input: any) {
}
}
Here comes the rub: in order to allow object types, you would have to add an overload signature of someMethod(input: Object);
or someMethod(input: {});
. As soon as you do this, functions would become allowed, because function inherits from object.
If you could narrow down object
to something less general, you could simply add more and more overloads (yikes) for all the types you want to allow.
Solution 2:
You can do this in TypeScript 2.8 with conditional types.
type NotFunc<T> = Exclude<T, Function>
function noFunc <T> (notF: T & NotFunc<T>) { return notF }
const f = () => 2
noFunc(f) // error!
noFunc(f()) // compiles!
If the type system can decide that T
extends Function
(i.e., T
is a function) then the type will be never
, which is a compile-time error. Otherwise, the type will be T
and your code will compile.
However, you should read this to more clearly understand what is happening:
// type NotFunc<T> = T extends Function ? never : T
type NotFunc<T> = Exclude<T, Function>
// the imporant cases for us are any and {}
type AnyNotFunc = NotFunc<any> // any
type ObjNotFunc = NotFunc<{}> // {}
type NullNotFunc = NotFunc<null> // never
// some functions, explicitly typed and inferred
const f: Function = () => 2
const ff = () => 2
const g: Function = (): null => null
const gg = (): null => null
// so a function like this won't work:
function badNoFunc <T> (notF: NotFunc<T>) { return notF }
// these all compile, because T is inferred as {} and NotFunc<{}> is just {}
badNoFunc(f)
badNoFunc(g)
badNoFunc(ff)
badNoFunc(gg)
// so need the T & NotFunc<T> to give the compiler a hint as to the type of T
function noFunc <T> (notF: T & NotFunc<T>) { return notF }
// now f is correctly inferred to be Function
noFunc(f) // error! f is assignable to Function
noFunc(g) // error! g is assignable to Function
noFunc(f()) // OK! 2 is not assignable to Function
// but we would expect g() === null to be never since NotFunc<null> === never
noFunc(g()) // OK? even though null is assignable to Function?
noFunc<null>(g()) // Error! Ah, type Function represents () => any but NotFunc<null> is never
// if we use the implicitly typed version, gg, the compiler infers the null return value correctly
noFunc(gg()) // Error! Correct
noFunc(ff) // error! The type is correctly inferred to be function
noFunc(gg) // error! The type is correctly inferred to be function
noFunc(ff()) // OK! 2 is not assignable to Function
Takeaways:
- The OP can do what they want
- The
Function
type is as bad asany
, so avoid it
Solution 3:
Here is an approach that defines all valid (non-function) values and then uses a recursive definition. I think this works for my case and hopefully for anyone else who comes across this question.
Example on Typescript Playground
type NoFunctionValue =
boolean
| string
| number
| null
| undefined
| NoFunctionObject
| NoFunctionArray
interface NoFunctionObject {
[key: string]: NoFunctionValue
}
interface NoFunctionArray extends Array<NoFunctionValue> { }
// Try putting a function anywhere in here to see error
const text: NoFunctionObject = {
bool: true,
str: 'string',
num: 7,
nul: null,
undef: undefined,
arr: [true, 'string', 7, null, undefined],
obj: {
bool: true,
str: 'string',
num: 7,
nul: null,
undef: undefined,
arr: [true, 'string', 7, null, undefined]
}
}
Solution 4:
With typescript 1.8, you can get pretty close if you define function as something that has all 4 properties: caller, bind, apply and call:
interface NoCaller {
caller?: void;
}
interface NoBind {
bind?: void;
}
interface NoApply {
apply?: void;
}
interface NoCall {
call?: void;
}
type NotAFunction = NoCaller | NoBind | NoApply | NoCall; // if it fails all 4 checks it's a function
function check<T extends NotAFunction>(t: T) {
// do something
}
function f() {
}
class T {
}
var o = new T();
check({});
check(o);
check({caller: 'ok', call: 3, apply: this});
//check(f); // fails
//check(T); // also fails: classes are functions, you can't really escape from javascript
Surprisingly, the error message is not that bad:
error TS2345: Argument of type '() => void' is not assignable to parameter of type 'NoCaller | NoBind | NoApply | NoCall'.
Type '() => void' is not assignable to type 'NoCall'.
Types of property 'call' are incompatible.
Type '(thisArg: any, ...argArray: any[]) => any' is not assignable to type 'void'.