-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Comments
Seems conceptually similar to 'outersection' types, where you'd express it something like 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 In a way, it would seem consistent under |
Yes, |
Maybe a new builtin type name like |
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 |
Beat me to it :) For reference, see #5451 (comment) Edit: Although atleast for the first case, a generic |
On the output side, it would be |
Thanks. It's a bit wordy, but I suppose the set of primitive types is well-known. Is there any reason to use |
For callback parameter type annotations, one may be preferable over the other depending on the scenario. |
If function return types in |
For return types, it's probably best to leave it as |
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 |
This would be only for documentation purposes as TS allows dereferencing Ideas:
|
Yeah, I'll close this. |
Although I don't think that having It would be more correct to say 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 We have talked about a |
@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 I was pointing out that Personally I can live with 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 It will be updated to include new primitives, if/when any are added to the language (see recent addition of symbols). |
I am using 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 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 GuardsThis 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 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 DeclarationsIf 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.
|
For me, the use-case for |
@Arnavion @RyanCavanaugh I think this should be reopened - there's another simple use case for the 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, 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 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.
I don't think there's any way to declare the equivalent of |
null-checks and undefined-checks! That's one of the really really big points to using static analysis to begin with. 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 Just want to also echo what was said earlier. We don't need over-engineered sophistication such as |
Since this thread comes up in google searches. export type thing = { [key: string]: any };
export type anything = thing | undefined | null; |
I'm realizing now that a return type of 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- In any case, your example is no different then just having the 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 Actual @cwharris see above example. I can understand where you're coming from but I care for catching For example, if I add 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 |
I made an error in judgement when using 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 I believe that using this type of approach in conjunction with the 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...
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. 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 // 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:
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 - 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 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 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 |
I think the core of the issue is that people assume TypeScript only works with types. It ignores anything which does not have a type. This includes What some people want is for the TypeScript team to spend time making a But instead of spending time compounding the Personally, I think 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 |
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 |
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:
|
@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 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 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. |
Algebraic data type operators exist in TS. declare function f(x : NonNullable<any>)
declare const a : string | null
f(a) //works``` |
And by the way, |
I'm not sure if this would help, but at least this is a possible application:
If you pass |
Try it out, it does not work. You'll find the appalling fact that |
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. |
To declare a variable that can be of any type you can use |
I'm ok with doing 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. |
With optional chaining, I think there is another use case?
Because an optional chained
Where as if there was a non-null
|
While updating the various .d.ts to have
| null
and| undefined
, I came across some typings that useany
but don't allownull
orundefined
.Object.defineProperty:
o
and the returned value cannot benull
orundefined
.Object.setPrototypeOf:
o
and the returned value cannot benull
orundefined
.proto
can benull
but 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.
The text was updated successfully, but these errors were encountered: