Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
Arnavion opened this issue Mar 23, 2016 · 59 comments
Closed

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

Arnavion opened this issue Mar 23, 2016 · 59 comments

Comments

@Arnavion
Copy link
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.

@yortus
Copy link
Contributor

yortus commented Mar 23, 2016

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
Copy link
Contributor Author

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

@yortus
Copy link
Contributor

yortus commented Mar 23, 2016

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
Copy link
Contributor Author

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
Copy link
Contributor

yortus commented Mar 23, 2016

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

@jeffreymorlan
Copy link
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
Copy link
Contributor

yortus commented Mar 23, 2016

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
Copy link
Contributor Author

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
Copy link
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
Copy link
Contributor Author

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
Copy link
Member

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

@yortus
Copy link
Contributor

yortus commented Mar 23, 2016

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
Copy link
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
Copy link
Contributor Author

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
Copy link

jods4 commented Mar 27, 2016

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

@mhegazy
Copy link
Contributor

mhegazy commented Mar 29, 2016

@Arnavion assuming #1809 is in, this request would be just a shorter name for string | number | boolean | symbol | object?

@Arnavion
Copy link
Contributor Author

Yeah, I'll close this.

@jods4
Copy link

jods4 commented Mar 29, 2016

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.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 29, 2016

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.

@jods4
Copy link

jods4 commented Mar 29, 2016

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

@mightyiam
Copy link

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

@slavovojacek
Copy link

slavovojacek commented Apr 4, 2017

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>();
}

See https://github.com/Threestup/monads

@tinganho
Copy link
Contributor

tinganho commented Oct 5, 2017

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
}

@cwharris
Copy link

cwharris commented Oct 17, 2017

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

@jez9999
Copy link

jez9999 commented Oct 26, 2017

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

@mmkal
Copy link

mmkal commented Dec 27, 2017

@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));
    }
};

@jeskew
Copy link

jeskew commented Feb 7, 2018

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.

@srcspider
Copy link

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

@srcspider
Copy link

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;

@cwharris
Copy link

cwharris commented Mar 12, 2018

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

@srcspider
Copy link

@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

@cwharris
Copy link

cwharris commented Mar 13, 2018

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:

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.

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

@RyanCavanaugh
Copy link
Member

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.

@cwharris
Copy link

cwharris commented Mar 14, 2018

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

@dcodeIO
Copy link

dcodeIO commented Mar 14, 2018

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.

@RyanCavanaugh
Copy link
Member

@dcodeIO

declare function assert<T>(isTrueish: T | null | undefined | 0 | "", message?: string): T;

declare const p: null | string;
const p2 = assert(p); // p2: string

@dcodeIO
Copy link

dcodeIO commented Mar 14, 2018

Oh, thanks. Well, I guess I don't have a use case then and can just go with T | null on the parameter, yey :)

@rdhelms
Copy link

rdhelms commented Mar 14, 2018

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 is an optional variable
  • sandwich is an optional variable
  • If sandwich DOES exist, its type property should be the type of sandwichType, but it's NOT optional

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Mar 14, 2018

@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"
  }
});

@rdhelms
Copy link

rdhelms commented Mar 14, 2018

Nice - it actually looks like the new Exclude Predefined Conditional Type is exactly what I would want:
#21847

Will just have to wait for 2.8!

@ORESoftware
Copy link

ORESoftware commented Jul 8, 2018

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

@tswordyao
Copy link

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

@lppedd
Copy link

lppedd commented May 9, 2019

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

@lemon-clown
Copy link

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

@nuts-n-bits
Copy link

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
Copy link

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.

@t-ricci-enhancers
Copy link

t-ricci-enhancers commented Sep 29, 2019

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

@cyberixae
Copy link

interface State {
    message?: some;
}

@mmkal Seems to be related to #13195

@nuts-n-bits
Copy link

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.

@t-ricci-enhancers
Copy link

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

@nuts-n-bits
Copy link

nuts-n-bits commented Oct 2, 2019

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
Copy link

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
Copy link

cyberixae commented Oct 14, 2019

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
Copy link

mykohsu commented Apr 6, 2023

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
}

@microsoft microsoft locked as resolved and limited conversation to collaborators Apr 6, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests