Skip to content

Some way to express a non-null / non-undefined any would be nice #7648

Closed
@Arnavion

Description

@Arnavion
Contributor

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;

    o and the returned value cannot be null or undefined.

  • 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;

    o and the returned value cannot be null or undefined. proto can be null but not undefined.

  • 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.

Activity

yortus

yortus commented on Mar 23, 2016

@yortus
Contributor

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"

Arnavion

Arnavion commented on Mar 23, 2016

@Arnavion
ContributorAuthor

Yes, I remember, which is why I'm asking for a much easier form.

yortus

yortus commented on Mar 23, 2016

@yortus
Contributor

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.

Arnavion

Arnavion commented on Mar 23, 2016

@Arnavion
ContributorAuthor

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.

yortus

yortus commented on Mar 23, 2016

@yortus
Contributor

Maybe a new builtin type name like some or something, where some | null | undefined ≡ any

jeffreymorlan

jeffreymorlan commented on Mar 23, 2016

@jeffreymorlan
Contributor

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
yortus

yortus commented on Mar 23, 2016

@yortus
Contributor

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.

Arnavion

Arnavion commented on Mar 23, 2016

@Arnavion
ContributorAuthor

Beat me to it :) For reference, see #5451 (comment)

Edit: Although atleast for the first case, a generic T extends {} with return type T should work?

RyanCavanaugh

RyanCavanaugh commented on Mar 23, 2016

@RyanCavanaugh
Member

{} 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)

Arnavion

Arnavion commented on Mar 23, 2016

@Arnavion
ContributorAuthor

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?

RyanCavanaugh

RyanCavanaugh commented on Mar 23, 2016

@RyanCavanaugh
Member

For callback parameter type annotations, one may be preferable over the other depending on the scenario.

yortus

yortus commented on Mar 23, 2016

@yortus
Contributor

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.

RyanCavanaugh

RyanCavanaugh commented on Mar 23, 2016

@RyanCavanaugh
Member

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.

Arnavion

Arnavion commented on Mar 23, 2016

@Arnavion
ContributorAuthor

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.

jods4

jods4 commented on Mar 27, 2016

@jods4

This would be only for documentation purposes as TS allows dereferencing any or assigning to non-null variables.

Ideas:

  • Should we rather annotate methods that may return null as any | null? This would be consistent with other types and OK for doc.
  • Is ! 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!.

35 remaining items

lemon-clown

lemon-clown commented on Aug 15, 2019

@lemon-clown

maybe you can try use T extends null ? never : T, as follows:
image

nuts-n-bits

nuts-n-bits commented on Aug 24, 2019

@nuts-n-bits

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.

declare function f(x : NonNullable<any>)
declare const a : string | null

f(a) //works```
nuts-n-bits

nuts-n-bits commented on Aug 24, 2019

@nuts-n-bits

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.

ghost
cyberixae

cyberixae commented on Oct 1, 2019

@cyberixae
interface State {
    message?: some;
}

@mmkal Seems to be related to #13195

nuts-n-bits

nuts-n-bits commented on Oct 1, 2019

@nuts-n-bits

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.

ghost
nuts-n-bits

nuts-n-bits commented on Oct 2, 2019

@nuts-n-bits

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.

michalburger1

michalburger1 commented on Oct 14, 2019

@michalburger1

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.

cyberixae

cyberixae commented on Oct 14, 2019

@cyberixae

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.

mykohsu

mykohsu commented on Apr 6, 2023

@mykohsu

With optional chaining, I think there is another use case?

const a: any = null;
const chained: string|null = a?.foo; // This should error
const nullable: Exclude<any, undefined> = a?.foo; // This should error
const neverNull: Exclude<any, null> = a?.foo;

Because an optional chained any is still any, a subsequent check on the assigned nullable parameter can make incorrect assumptions:

if (chained !== null || nullable !== null) {
  console.log(chained.length); // throws because chained is undefined
  console.log(nullable.length); // throws because nullable is undefined
}

Where as if there was a non-null any, comparing the optional chained result to null can be caught at compile time:

if (neverNull !== null) {
  // always true
}
locked as resolved and limited conversation to collaborators on Apr 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @srcspider@mightyiam@jeskew@jez9999@mykohsu

        Issue actions

          Some way to express a non-null / non-undefined any would be nice · Issue #7648 · microsoft/TypeScript