TypeScript: Some way to express a non-null / non-undefined any would be nice
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.
-
Object.defineProperty:
/** * 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;oand the returned value cannot benullorundefined. -
Object.setPrototypeOf:
/** * 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;oand the returned value cannot benullorundefined.protocan benullbut notundefined. -
etc.
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.
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 34
- Comments: 59 (23 by maintainers)
This type already exists. It’s
{}Maybe a new builtin type name like
someorsomething, wheresome | null | undefined ≡ anyThe following example, shows that
string | number | boolean | symbol | objectis not the same asany - null - undefined.anydoesn’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 doNonNullable<unknown>because it’s not a union type, sostring | number | boolean | symbol | bigint | objectseems to be the only way to do this right now.Since this thread comes up in google searches. This is the best you can do to get at least some errors caught:
I’m ok with doing
string | number | boolean | ...as a workaround but is there some way to display a custom alias likeDefinedorSomein 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.
@dcodeIO
I am using
T | null | undefinedand it seems to be working well:See https://github.com/Threestup/monads
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).
@mmkal Seems to be related to #13195
maybe you can try use
T extends null ? never : T, as follows:@RyanCavanaugh @cwharris
Of course they’re not the same thing.
@RyanCavanaugh
null-checks and undefined-checks!
That’s one of the really really big points to using static analysis to begin with.
anyis 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:
This is how we would like it to look:
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 ofnever[]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 useanythere 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-inthingtype that’s exactly likeanyexcept it doesn’t allownull/undefined.@Arnavion @RyanCavanaugh I think this should be reopened - there’s another simple use case for the
someidea, which isn’t covered byobjector{}: forcing consumers of a type or interface to handle a falsy value explicitly.What we have to do currently:
in the above,
state.messagecould 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 assumestate.messageis there. If we could do:The compiler would highlight the bug in the code above and correctly force me to fix it like this:
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.anyis given carte blanche - ignored by type checking altogether. It’s a way to enforce explicitly declaring the type of all variables (a lanoExplicitAny) 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.anyis 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).anyis 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
sometype 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
anyconfusion by adding asometype, it looks like the TypeScript teams has decided to focus on features thatcan'tbe easy added without modifying the compiler.Personally, I think
someis a bad use of TypeScript’s development resources. You can write your ownsomein 10 seconds, and things will just work out how you want them to. Plus you get to tell all your friends how annoyinganyis, and how it’s the only type in TypeScript that isn’t a type.@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
anyare just unknownobjectshapes.I was pointing out that
anyhas 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 fromanytoobjectis a breaking change, see the examples in my previous comment.Personally I can live with
anybeing 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 usingany | nullfor nullablesanyand justanyfor the non-nullable ones.Again, it won’t change compilation but it can be seen as helpful from a documentation perspective.
{}would be the equivalent type from the input side.On the output side, it would be
string | number | boolean | object(note thatobjectis unimplemented, see #1809)