While updating the various .d.ts to have | null
and | undefined
, I came across some typings that use any
but don't allow null
or undefined
.
typescript
/**
* Adds a property to an object, or modifies attributes of an existing property.
* @param o Object on which to add or modify the property. This can be a native JavaScript
* object (that is, a user-defined object or a built in object) or a DOM object.
* @param p The property name.
* @param attributes Descriptor for the property. It can be for a data property or an accessor
* property.
*/
defineProperty(o: any, propertyKey: PropertyKey, attributes: PropertyDescriptor): any;
o
and the returned value cannot be null
or undefined
.
typescript
/**
* Sets the prototype of a specified object o to object proto or null. Returns the object o.
* @param o The object to change its prototype.
* @param proto The value of the new prototype or null.
*/
setPrototypeOf(o: any, proto: any): any;
o
and the returned value cannot be null
or undefined
. proto
can be null
but not undefined
.
I think there is value in being able to express both "This can be any type including null and undefined" and "This can be any type excluding null / undefined", but there is no way to express the latter.
Seems conceptually similar to 'outersection' types, where you'd express it something like any - null - undefined
, where you start with a type and 'subtract' other types from it.
That issue is closed as too complex but there's a comment about it from a recent backlog slog that says: _"Revisit in context of type operators"_
Yes, I remember, which is why I'm asking for a much easier form.
This issue is probably going to come up more often when code starts using --strictNullChecks
. In that mode types like string
and number
will exclude null
and undefined
, but what about any
? Does that still include null
and undefined
under strict null checks?
In a way, it would seem consistent under --strictNullChecks
that any
excludes null
and undefined
, and you would have to be explicit to include them, i.e. any | null | undefined
. OTOH that seems like a logical contradiction of the special semantics of any
.
Yes, --strictNullChecks
is the context of this issue, and yes since nullability is via union types, any
is allowed to be null
and undefined
. Hence this issue.
Maybe a new builtin type name like some
or something
, where some | null | undefined ≡ any
This type already exists. It's {}
declare function f(x: {}): void;
f(0); // ok
f(""); // ok
f(true); // ok
f({}); // ok
f(null); // error
f(undefined); // error
I don't think {}
is the same thing at all. Using {}
as the return type in the OP's examples have a very different meaning than any - null - undefined
or however it could be expressed.
Beat me to it :) For reference, see https://github.com/Microsoft/TypeScript/issues/5451#issuecomment-152257673
Edit: Although atleast for the first case, a generic T extends {}
with return type T
should work?
{}
would be the equivalent type from the input side.
On the output side, it would be string | number | boolean | object
(note that object
is unimplemented, see #1809)
Thanks. It's a bit wordy, but I suppose the set of primitive types is well-known.
Is there any reason to use {}
over that for the input side?
For callback parameter type annotations, one may be preferable over the other depending on the scenario.
If function return types in .d.ts
files are changed from any
to string | number | boolean | object
(for the purposes of excluding null
or undefined
as in the OP's examples), wouldn't that break a lot of user code? Type assertions would have to be added where they were previously unnecessary on any
types. Not saying that's good or bad, just a big breaking change.
For return types, it's probably best to leave it as any
. I'm not sure what use there is in saying "any but not null or undefined" since you're able to assign it to a non-null type anyway.
It would be self-documenting that it won't return a null. It may be useful for user functions as well that can return any object but never null, like a parse(input, rule): output
function.
This would be only for documentation purposes as TS allows dereferencing any
or assigning to non-null variables.
Ideas:
any | null
? This would be consistent with other types and OK for doc.!
going to fly for types as well? It could be useful with generics like: function nonNulls<T>(array: T[]): T![]
. If it does then maybe we should simply accept any!
.@Arnavion assuming #1809 is in, this request would be just a shorter name for string | number | boolean | symbol | object
?
Yeah, I'll close this.
Although I don't think that having any!
has a lot of value, I would point out that replacing any
with string | number | boolean | symbol | object
is not _exactly_ the same.
It would be more correct to say setPrototypeOf(..): object
(given #1809).
But doing so is a breaking change:
let x = Object.setPrototypeOf(a, Whatever); // : object
x.doSomething(); // Error: no .doSomething() on object. Ok with any.
let y : Sometype = Object.setPrototypeOf(a, Whatever); // Error: Missing cast. Ok with any.
let z = <Sometype>Object.setPrototypeOf(a, Whatever); // ok with both.
given that the current way to specify nullable types is T | null
, T | undefined
or T | null | undefined
; the system is already verbose. so type Concrete = string | number | boolean | symbol | object
does not seem too bad.
We have talked about a !
type operator to get the non-nullable version of a type. but we have not been to keen on adding new type operators, just because of the complexity both to the language and the implementation at the time being.
@mhegazy I have no problem with verbosity, especially since (a) you can define aliases and (b) few APIs actually returns all kind of types. Quite often any
are just unknown object
shapes.
I was pointing out that any
has one big difference to the union of all types: _it is a special type that bypasses all compiler checks!_ This is why changing a return type from any
to object
is a breaking change, see the examples in my previous comment.
Personally I can live with any
being unclear about nulls (it's not checked anyway) but if we want to support better documentation without introducing new type operators, I suggested previously that we start the habit of using any | null
for nullables any
and just any
for the non-nullable ones.
Again, it won't change compilation but it can be seen as helpful from a documentation perspective.
I'm sorry if I'm not involved in the project enough to know whether this conversation is finished, but here's my take:
How about a new global that is an alias to string | number | boolean | symbol | object
.
It will be updated to include new primitives, if/when any are added to the language (see recent addition of symbols).
I am using T | null | undefined
and it seems to be working well:
export function Some<T>(_:T|null|undefined):_Some<T>|_None<T> {
return assert_some(_) ? new _Some(_ as T) : new _None<T>();
}
The following example, shows that string | number | boolean | symbol | object
is not the same as any - null - undefined
.
function f(p: string | number | boolean | symbol | object) {
p.something // error
}
function f(p: any - null - undefined) {
p.something // no error
}
😄 Function Overloads and Type Guards
-
This is in all likelihood what should be used in the case that you want to have a non-null value. It gives you the appropriate intellsense listings, automatically casts values to the correct types within the guarded statements, and returns specific types instead of a "yeah I mean it *could* be a string, but it's usually a number"
type. TypeScript will also prevent you from invoking the method with a: number
and b: string
, and vice-versa. Importing a specially declared type will solve the "but it really is a number | string
," issue in a non-assuming manner.
import { numberOrBoolean } from "./my-types";
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isBoolean(x: any): x is boolean {
return typeof x === "string";
}
function combine(a: number, b: number): number
function combine(a: boolean, b: boolean): boolean
function combine(
a: numberOrBoolean,
b: numberOrBoolean
)
: numberOrBoolean
{
if (isNumber(a) && isNumber(b)) {
return a + b;
}
if (isString(a) && isString(b)) {
return a && b;
}
throw new Error("overload declarations prevent this.");
}
😢 Global Type Declarations
-
If anybody wants to ingrain this pattern in to their development environment and company culture... here's how to do it. Also, don't do it. The only valid use case is better solved with function overloads.
app.ts
-
/// <reference path="<types>/global.d.ts"/>
<types>/global.d.ts
-
declare type actual = string | number | boolean | symbol | object;
For me, the use-case for any!
is for type-documenting existing JS libraries. If I'm documenting a library where there's a method that tries to deal with the input depending on its type, but assumes it will not be undefined/null, it really is begging for a concise way of saying "not undefined or null".
@Arnavion @RyanCavanaugh I think this should be reopened - there's another simple use case for the some
idea, which isn't covered by object
or {}
: forcing consumers of a type or interface to handle a falsy value explicitly.
What we have to do currently:
interface State {
message: any;
}
const thingThatUsesState = (state: State) => {
if (state.message.text === "foo") {
console.log("message text is foo");
} else {
console.log(JSON.stringify(state.message));
}
};
in the above, state.message
could be null or undefined, and we'll get a runtime error. TypeScript doesn't protect me against that by giving a compile-time error when I assume state.message
is there. If we could do:
interface State {
message?: some;
}
The compiler would highlight the bug in the code above and correctly force me to fix it like this:
const thingThatUsesState = (state: State) => {
if (!state.message) {
console.log("message is missing");
} else if (state.message.text === "foo") {
console.log("message text is foo");
} else {
console.log(JSON.stringify(state.message));
}
};
Trying to attach a non-null assertion using the concrete
union suggested above does not seem to be working. I'm trying to write a generic "normalize" function that will be executed on user input only if that input is defined:
type Defined = string|number|boolean|symbol|object|null;
interface ConfigurationPropertyDefinition<
InputType,
ResolvedType extends InputType
> {
normalize?: {
(value: InputType & Defined): ResolvedType;
}
}
In a case where a property can be a string or a function that returns a string and is not required, the compiler emits a warning with a less than helpful message. InputType
is string|() => string|undefined
, so I would expect the intersection of InputType & Defined
to be narrowed to string|() => string
. However, the compiler emits an error with the following message:
Type 'undefined & Number' provides no match for the signature '(): string'.
I don't think there's any way to declare the equivalent of InputType - undefined
with intersection types.
@RyanCavanaugh
For return types, it's probably best to leave it as any. I'm not sure what use there is in saying "any but not null or undefined" since you're able to assign it to a non-null type anyway.
null-checks and undefined-checks!
That's one of the really really big points to using static analysis to begin with. any
is flawed in that it's meaning is "do whatever" but in many contexts what we really want to say is "it's whatever [that's not null/undefined]" and it's _really easy_ to use it like that.
_I'd say this is a failure of english if nothing else. Same words but totally different meanings "it's a thing that exists" vs "this is not checked." This is terrible for code readability too._
This is the problem: "any = can be anything including null or undefined but will NEVER checks for it"
Consider this code:
get_db_entry(query, parseEntry);
function processEntry(err: any, entry: any) {
entry['someKey'] = [];
// ^ entry could very well be undefined here, typescript don't care
}
This is how we would like it to look:
get_db_entry(query, parseEntry);
function processEntry(err: any, entry: thing|undefined) {
entry['someKey'] = [];
// ^ Error: entry could be undefined
}
// or if we omit use just "thing" there then the error would be on get_db_entry
// because it's getting passed an incompatible function
There's no way to declare a "thing" type either, since you can't do recursive types. Note the []
there, that actually would result in some sort of never[]
assignment error one way or another. It's nearly impossible to declare it with out treating it as a special case each time (which is at odds to you wanting to use any
there to begin with).
Just want to also echo what was said earlier. We don't need over-engineered sophistication such as any - null - undefined
. We just want a built-in thing
type that's exactly like any
except it doesn't allow null
/ undefined
.
_Since this thread comes up in google searches._
This is the best you can do to get at least some errors caught:
export type thing = { [key: string]: any };
export type anything = thing | undefined | null;
We just want a built-in thing type that's exactly like any except it doesn't allow null / undefined.
object | string | boolean | symbol | number
?
@RyanCavanaugh I can't see why object | string | boolean | symbol | number
isn't a valid solution, or why defining that combination globally in a project is sub-par for those who want it.
@RyanCavanaugh @cwharris
Of course they're not the same thing.
export type thing = object | string | boolean | symbol | number;
function example1(d: thing) {
d.someProp = 5;
// runtime/test.ts(4,7): error TS2339: Property 'someProp' does not exist on type 'thing'.
// Property 'someProp' does not exist on type 'string'.
}
If you want to do janky stuff in the function body, just use a hidden implementation signature:
function example1(d: thing): void;
function example1(d: any) {
}
@srcspider "overloads" are a better solution to the case you presented, as @RyanCavanaugh shows. His example accepts an any
, which sort of negates the benefits, but in general they communicates far more information to the compiler.
For the general return type scenario, is { [key:string]: any }
presenting you with an issue?
I'm realizing now that a return type of { [key: string]: any }
wont provide enough information to prevent indexing an undefined property - which makes sense, because the consumer doesn't have a contract for what properties will be provided on the return value, even though it will be guaranteed that none of those returned properties will have a value of null
or undefined
.
A better solution for this case is overloads / multiple signatures:
function operate(a: string, b: string): string;
function operate(a: number, b: number): number;
function operate(a: any, b: any): any
{
if (typeof a == "string" && typeof b == "string")
{
return `${a} ${b}`;
}
if (typeof a == "number" && typeof b == "number")
{
return a * b;
}
throw new Error("no matching implementation for given arguments");
}
var foo = operate(5, 6); // foo is known to be a number
var bar = operate("5", "6"); // bar is known to be a string
var baz = operator(5, "x"); // error
@RyanCavanaugh again your solution is not solving the problem at all.
It's not a case of just the return type, or just the parameter, or just the call site, but rather a case of all of them put together. With a non-null
/non-undefined
any
you can say "this parameter cant be empty", "this function never returns empty" and "inside this function this value is never empty" (and it's important for those to happen at the same time, not just be a "you can only opt-in to one of them" as with current solutions)
In any case, your example is no different then just having the any
signature, isn't it? Since that will accept anything whatsoever, so why would the other signature affect it? I don't see the case where that example forces an error check.
_Case_
export type thing = object | string | boolean | symbol | number;
export function example(d: thing|undefined);
export function example(d: any) {
d.someProp = 5;
}
_Compiles To_
"use strict";
exports.__esModule = true;
function example(d) {
d.someProp = 5;
}
exports.example = example;
_Expected_
Error: d may be undefined
_Actual_
No error.
@cwharris see above example. I can understand where you're coming from but I care for catching null
/undefined
over other type related errors because the other type related errors can be seen though a lot easier then null
/undefined
.
For example, if I add example(undefined)
at the end of the example above, then that still will not generate any errors. But it's very clear that it's not something the example
function is suppose to handle.
Both you and @RyanCavanaugh have given examples that ignore that the implementation inside the function is free to ignore the constraint given the definition.
With regard to "jankiness", if possible I'll use generics to dynamically enforce, or whenever possible write the type (so long as the type information is actually not redundant to the process).
However there are plenty of cases where its very useful (and very practical) to have a not-nothing type. For example function doThing<T = NonNothing>(thing: T);
. Or let value = 3rdPartyProvider(); firstPartyStuff(value); /* ...somewhere deep in firstPartyStuff... */ 3rdPartyConsumer(value)
(ie. don't care what it is so long as it's not nothing). Or parseThing(userinput: NotNothing): InputErrors
(ie. all checks for nothingness have to happen outside of the parseThing
function). etc etc
In any case, your example is no different then just having the any signature, isn't it? Since that will accept anything whatsoever, so why would the other signature affect it? I don't see the case where that example forces an error check.
I made an error in judgement when using any
as the type for the d
parameter in the implementation of the operate
function as it does not accurately portray the level of type checking available within the implementation's body. The implementation could just as easily be defined as...
function operate(a: string, b: string): string;
function operate(a: number, b: number): number;
function operate(a: number | string, b: number | string): any { ... }
This syntax effectively hides the implementation, and type-checks only against the first two declarations - the compiler will present an error if you provide any argument combination other than two strings or two numbers. The type system is obligated to inform the consumer that the return value is strictly a string in the first case, and a number in the second case. It does not allow the consumer to believe the result is any
or even string | number
.
I believe that using this type of approach in conjunction with the strict
compilation setting will give you two-thirds of what you are asking for - type checking of arguments as well as a specific return type (for both of which you are able to provide your own non-null
non-undefined
types).
I find this syntax to be cumbersome and unintuitive - genuine overloads are simply easier to read and understand, in my opinion, and they enforce type restraints on the provided arguments, return value, and within the implementation of each overload - a feature I believe is at the root of your interest/concern, if I understand you correctly.
Which brings me to...
... "inside this function this value is never empty"
I believe I understand what you are saying here. In other languages, such as C# for example, you can do the following:
```C#
public int Add(int a, int b) => a + b;
public string Add(string a, string b) => $"{a} {b}";
This in-so-much-as-it-matters (outside of assembly mismatch and reflection) forces the compiler to operate exactly as you'd expect. `a` and `b` are of either `int` or `string` within the two methods, respectively, and the body of those methods are required to respect that or suffer compile-time errors. This is the feature that is not currently available within TypeScript in any form whatsoever, *aside from implementing multiple methods with different names* - which is an API nightmare.
If I am wrong in thinking that this is the third (and potentially most important) feature you are describing, please correct me and ignore the rest of this comment.
Assuming the previous thinking to be accurate, I would like to provide a reason for why this feature does not currently exist in TypeScript and why it *may* not be a good idea to add that feature to this language *specifically*...
With the design principal that TypeScript is a super-set of JavaScript and transpiles directly to a given JavaScript version to be consumed by engines which support said version, let's go back to the `operate` example I provided and instead implement it using a hypothetical overload feature to see how the resulting JavaScript might look.
```TypeScript
// using hypothetical overload feature
function operate(a: string, b: string): string
{
return `${a} ${b}`; // always checked to be strings
}
function operate(a: number, b: number): number
{
return a + b; // always checked to be numbers
}
var x = operate("foo", "bar");
var y = operate(1, 2);
This guarantees:
1) The arguments are either string, string
or number, number
, respectively.
2) The return type is either string
or number
, respectively.
3) The parameters used within the methods are of the types declared in the method signature).
The resulting JavaScript could look a number of ways. Let's start by operating under the super-set design principal employed by Typescript, as defined by the Typescript design goals, specifically with regards to goals 7 and 8 - Preserve runtime behavior of all JavaScript code.
and Avoid adding expression-level syntax
.
function operate_string_string(a, b)
{
return `${a} ${b}`;
}
function operate_number_number(a, b)
{
return a + b;
}
var x = operate_string_string("foo", "bar");
var y = operate_number_number(1, 2);
This meets the requirement for both goals 7 and 8, but looks absolutely horrendous and arguably (if not explicitly) violates goals 4, 5, 7, and 11. Additionally, it begs the question, "what do we name the function if there already exists another function named operate_string_string
?" That question is relevant regardless of what approach we take to renaming the functions during transpile. So lets take a different approach.
function operate(a, b)
{
if (typeof(a) == "string")
{
return `${a} ${b}`;
}
if (typeof(b) == "string")
{
return a + b;
}
throw new Error(...);
}
var x = operate("foo", "bar");
var y = operate(1, 2);
This arguably conforms to some of the goals, but presents serious, and perhaps unresolvable, problems for any types where typeof(obj) == "object"
. Additionally, it violates goals 3, 5, 7, 8. and, in a way, 9.
This is at least one reason why overloads don't exist in TypeScript - there is not a clearly defined way to implement overloads and remain a super-set of JavaScript. And for that reason, it may not be a good idea to implement overloads, which is presumably the only way we could enforce parameter types within the body of a function. And thus the solution is left up to the developer on a per-function basis.
I can't figure out why { [key: string]: any }
isn't the right answer to @srcspider 's constraints. The function body says d.someProp = 5
in its body, which is manifest nonsense for the primitive types. TypeScript isn't obliged to provide built-in types which allow useless behavior.
I think the core of the issue is that people assume any
== { [key: string]: any } | object | string | boolean | symbol | number | null | undefined
, and that is not the case. any
is given carte blanche - ignored by type checking altogether. It's a way to enforce explicitly declaring the type of all variables (a la noExplicitAny
) even if it does not have a type.
TypeScript only works with types. It ignores anything which does not have a type. This includes any
. any
is a very good solution to a very hard problem, even if the naming is a bit weird (it implies that it is any of the built-in types). any
is a feature which could only be implemented as part of the TypeScript language.
What some people want is for the TypeScript team to spend time making a some
type which is an actual type defined as any (see how this is confusing?) built-in type except any (which is not a type).
But instead of spending time compounding the any
confusion by adding a some
type, it looks like the TypeScript teams has decided to focus on features that can't
be easy added without modifying the compiler.
Personally, I think some
is a bad use of TypeScript's development resources. You can write your own some
in 10 seconds, and things will just work out how you want them to. Plus you get to tell all your friends how annoying any
is, and how it's the only type in TypeScript that isn't a type.
type isnt = any;
type some = { [key: string]: some } | object | string | boolean | symbol | number | null | undefined;
type kinda = some | { [key: string]: any };
Having a similar use case, I believe.
/** Traps if the specified value is not true-ish, otherwise returns the value. */
declare function assert<T>(isTrueish: T, message?: string): T & object; // any better way to model `: T != null`?
Essentially: A function that takes a value of type T
, ensures that it is not false-ish (by throwing) and returning the value if it is true-ish. While the above solution with & object
kinda works, it somewhat feels wrong and leads to warnings with other tools that don't like intersections that much, for example tsickle.
@dcodeIO
declare function assert<T>(isTrueish: T | null | undefined | 0 | "", message?: string): T;
declare const p: null | string;
const p2 = assert(p); // p2: string
Oh, thanks. Well, I guess I don't have a use case then and can just go with T | null
on the parameter, yey :)
Here's a case where what I feel like I want is a "subtract undefined" operator, but maybe someone can enlighten me as to how else to do this properly:
interface IDataModel {
sandwichType?: string;
sandwich?: {
name: string;
type: IDataModel['sandwichType'] - undefined;
};
}
Basically trying to acomplish:
sandwichType
, but it's NOT optional@rdhelms Conditional types (2.8) can help:
type RemoveUndefined<T> = T extends undefined | infer R ? R : T;
interface IDataModel<T extends string> {
sandwichType?: T;
sandwich?: {
name: string;
type: RemoveUndefined<IDataModel<T>['sandwichType']>;
};
}
function takeModel<T extends string>(model: IDataModel<T>) {
}
// Errors
takeModel({
sandwichType: "rye",
sandwich: {
name: "bob"
}
});
Nice - it actually looks like the new Exclude
Predefined Conditional Type is exactly what I would want:
https://github.com/Microsoft/TypeScript/pull/21847
Will just have to wait for 2.8!
I have this related question - https://stackoverflow.com/questions/51236491/typescript-type-representing-everything-but-undefined
if someone wants SO points please take a look thx
I was thinking something like this would be nifty:
export type NotUndefined = !undefined
probably a dumb idea, but at least you get the picture
We just want a built-in thing type that's exactly like any except it doesn't allow null / undefined.
object | string | boolean | symbol | number
?
type yourType = object | string | boolean | symbol | number;
function f():()=>yourType{
return (window as any).returnAnyObj();
}
var res=f()
res.xxx=1 // error!
we just need any object, when use MemberExpression won't throw error
In case someone need examples, here is a question I had posted some time ago.
https://stackoverflow.com/questions/53612681/typescript-function-cannot-return-undefined
maybe you can try use T extends null ? never : T
, as follows:
Algebraic data type operators exist in TS. Exclude<T>
, NonNullable<T>
seemed nice, until I tried them with any
, and they do work with any
.
```ts
declare function f(x : NonNullable
declare const a : string | null
f(a) //works```
And by the way, object | string | boolean | number
is bad form. Not to mention it's object | string | boolean | number | symbol | bigint
as of comment. Each time a new primitive is added you need to update it.
I'm not sure if this would help, but at least this is a possible application:
type Nil = null | undefined;
type NotNil<T> = Nil extends T ? never : T extends Nil ? never : T;
export type HasValue<T> = { value: NotNil<T> };
const v: HasValue<number> = {
value: 10
};
If you pass undefined
or null
to NotNil<T>
it will resolve to never
, but you cannot use any
because undefined
and null
extend any
, so NotNil<any>
will resolve to never
interface State { message?: some; }
@mmkal Seems to be related to #13195
type Nil = null | undefined;
type NotNil= Nil extends T ? never : T extends Nil ? never : T;
Try it out, it does not work. You'll find the appalling fact that NotNil<any>
is now an alias of never
. This is because any extends anything.
I know it's not the same thing, you won't be able to use any but you're guaranteed you need to specify a type which could not be null
I know it's not the same thing, you won't be able to use any but you're guaranteed you need to specify a type which could not be null
I appreciate that, however the title of this issue is explicitly non-nullable any.
any
doesn't mean that the value can be of any type. It means "please disable all type checking thanks". It doesn't make a whole lot of sense to "disable type checking except for null checks".
To declare a variable that can be of any type you can use unknown
. Sadly, you can't do NonNullable<unknown>
because it's not a union type, so string | number | boolean | symbol | bigint | object
seems to be the only way to do this right now.
I'm ok with doing string | number | boolean | ...
as a workaround but is there some way to display a custom alias like Defined
or Some
in error messages instead of the full definition?
I'm working with generated code which contains lots of statements about things that are defined and the error messagages are extremely hard to read because of such long definitions. See https://github.com/maasglobal/maas-schemas-ts/blob/master/src/core/booking.ts for example.
Most helpful comment
This type already exists. It's
{}