-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Add an absent
type forbidding presence
#55143
Comments
absent
typing forbidding presenceabsent
type forbidding presence
There already exists The fact |
IMO this approach just sounds like a hack. The much cleaner approach would be to use the |
Interesting. It seems like a good move, but I am guessing that it is off in strict mode due to back compat issues. Do you know? If you are suggesting that that flag provides the suggested feature, I am sure it doesn't. It reduces the acceptable values of With optional abstract properties you could do abstract class Component<S extends object> {
abstract state?: S
} but this wouldn't require the derived class to actually set state. The problem is the same with or without exactOptionalPropertyTypes. Your state property would always be In order to narrow down from |
It's not even enabled in |
I don't see what this has to do with the suggestion. undefined and absent are different. In the framework example, using undefined requires each component to implement state even if not using it, which is what I am trying to avoid. In the second example you could do runtime checks on the values of properties, but you have no checking of your code and are relying on your own logic. With this suggestion typescript verifies the safety of your code, as opposed to needing to write, maintain and run automated tests to do the same checks. The absence of a variable or property is an important part of the meta-data on an object. If we can only talk of what an object has, and not of what an object does not have, we are missing part of the picture in describing that object. Imagine trying to describe a person with a missing left leg, while only being able to say what he has and not what he does not have. You can only create a description of a person who has his other limbs, and may or may not have his left leg. This is our current description of objects, unless I am missing something very important. |
There's a reason that some information goes on the property slot (optionality) and some goes on the type, and that there aren't any type modifiers which change information about the property slot. Once you have
I can understand why some scenarios seem to call for per-site |
It's the same.
Absolutely
In a trailing position its the same as not being there (TS blocks additional args), unless that is configurable.
This was covered, probably it is prohibited, as oxymoronic
OK good point. I'll have a think about it
I don't see the connection to this suggestion. The suggestion is about prohibiting props, not making them optional. Especially for reducing a Generic down to one or the other and not leaving it optional. |
Because under EOPT, your request is satisfied by type OrderUnpaid = {
price: number,
paymentId?: never
}
type OrderPaid = {
price: number,
paymentId: string
} |
I see. But that doesn't cover the first use case (which is my use case). Because I can't switch between So perhaps the issue is how to bring the ?: concept into a Generic or conditional Something like abstract class C<(S extends (object | ?never))> {
abstract state: S // extends-object or ?: never
} But maybe we are opening up new problems again. |
How does this differ from |
A property type
See #55121 |
Further thoughts (1) Concept: When working with logic, working with the absense of things can be just as important as the presence. TS currently has wide support for discussion things that are present, such as passing a type to a generic that will then be used or required. However TS does not have equal support for optionals or absense. An optional property can be declared with ?:, but this expression is not supported within generics, and cannot be passed around, subjected to conditionals or specified in uses of generic devices. Excluding a property requires the exactOptionalPropertyTyes flag, which is false by default, and may not be I found some small issues with how typescript unions types, and with how JS turns absence of values into undefined (such as assigning from a function that does not return). But I think they can be worked around and are not deal breakers. Of course if anyone and suggest cleaner solutions, that would be great! Functions(2) A function returning
Alternatively absent as a return type could simply be forbidden without major issue. This discussion does raise the question of whether (3) The use of function f<T extends unknown | absent = absent>(): (a: T) => void {
return (a: T) => {}
}
const f1 = f() // (a: absent) => {} == () => {}
const f2 = f<boolean>() // (a: boolean) => {} (4) Including function f<T>(a: T): T {
return a // this fails because an 'absent' 'a' leads to a === undefined
} Either we can avoid this by saying that returning undefined satisfies the constraint of (4A) The (4B) The opt-in route completing the generic function: function f<#T>(a: T): T {
// a is type 'unknown | absent'
// if we assigned a to const b, b would be type 'unknown | undefined', collapsing to unknown in this instance
if (arguments.length >= 1) {
// a is inferred to be type 'unknown'
return a
} else {
// a is inferred to be type 'absent'
// don't return
}
}
// note: if we modify 'arguments', then information may be lost, and TS
// will need to understand this during type inferences base on arguments
// e.g.
// arguments.length = 0
// delete arguments[0] (5) We can create uncallable functions const f<#U, #T> {
return (u: U, t: T) => undefined
}
const uf = f<absent, boolean>() // (u: absent, t: boolean) => undefined But I don't think this is a problem. "Just don't do it". Object Types(6) We can require that a property does not exist
(7) The Regardless of exactOptionalPropertyTypes,
(8) Type building with type T<#TT = whatever> = {
a: string
b: TT
}
type A = T // { a: string }
type B = T<boolean> // { a: string, b: boolean }
type C = T<absent> // { a: string, b: absent } One small issue with the above is that we can't allow the user to use type A = string | boolean
type B = boolean
type T<TT extends A | B> = {
a: TT
}
type U = T<string> // Allowed. We want this to error, because it is neither precisely A nor B Perhaps this could be solved with a new || operator requiring precisely A or B. But that is a separate issue (if anyone knows a solve for that please let me know). (9) As pointed out, In the case of exactOptionalPropertyTypes: false, we are not providing functionality that we missing, we seems like a good thing to do. exactOptionalPropertyTypes is false by default, surely there are many projects already committed to that mode. Uses(10) uses (10A) Type builders type whatever = unknown | absent
type MAKE<#A = whatever, #B = whatever, #C = whatever, #D = whatever> = {
a: A
b: B
c: C
d: D
e: string
f: number
}
type T = MAKE<string, number> // { a: string, b: number, e: string, f: number }
type U = MAKE<whatever, whatever, string, number> // { c: string, d: number, e: string, f: number } Currently we can do this with intersections, but with the type builder above, a library provider can provide a controlled type builder that is less error prone type Base = {
e: string
f: number
}
type T = Base & {
a: string
b: number
}
// what if we want to constrain the types that 'a' can use? We can do
type MAKE<A extends string | boolean | whatever = whatever, B extends unknown | whatever = whatever> = {
a: A
b: B
e: string
f: number
}
type U = MAKE<string, number> // error, 'string' not compatible with type 'string | boolean | whatever' (10B) Function builders. See point 2 above. (10C) Abstract classes As mentioned earlier in the ticket. Useful for React-like framework that want to have typesafe state abstract class Component<State extends object | absent = absent> {
abstract state: State
run() {
if ('state' in this) {
// do something with this.state
}
}
}
class Button extends Component {} // may not declare state property
class Button extends Component<{ text: string }> {} // must declare state property of given type We can use N.B. seems to be a bug in TS without useDefineForClassFields / target: >= ES2022, where (10D) Other uses? I feel like some creative people will find other ways to use this. It's a fundamental tool, that might be used in many other ways that don't immediately occur. Edit: Here is CustomOmit from Implement custom Omit Type in TypeScript implemented with interface Todo {
title: string;
description: string;
completed: boolean;
}
type CustomOmitWithNeverKeys<T,K extends keyof T> = {
[Key in keyof T as Key extends K ? never : Key] : T[Key]
}
type CustomOmitWithAbsent<T,K extends keyof T> = {
[Key in keyof T] : Key extends K ? whatever : T[Key]
}
type TodoCustomOmitWithNeverKeys = CustomOmitWithNeverKeys<Todo, "title">;
type TodoCustomOmitWithAbsent = CustomOmitWithAbsent<Todo, "title">; I think that's a cleaner and simpler way to implement Omit, and that's a good sign to me that this suggestion is going in the right direction. |
Suggestion
π Search Terms
type, absent, unset, none, missing, undeclared, not set, undefined
β Viability Checklist
β Suggestion
Add a type (working title
absent
) that forbids presence of the property or variable.π Motivating Example
The
absent
type would allow forbidding presence of a property on an object, forbidding a property or method in a derived class, and would allow abstract properties that can be configured to be required or not through generics.π» Use Cases
Consider a front end, react-like-framework, using classes.
The absent type combined with a generic would allow toggling between using state, and enforcing type safety on that state (requires initialisation), or not using state, which forbids presence of state. (This is my use case and motivation for this change)
Here is an example from Next today. The state property is not typesafe, and this code produces a run time error, despite no type errors.
To avoid this the framework would have to require state to be set in every component, with stateless components having to add state: undefined or state = undefined, or create a StatelessComponent variant. This is not the end of the world, it's a bit of an annoyance (seemingly enough of an annoyance that React has sacrificed type safety to avoid it).
Consider a payment processing system, where we want to prevent double processing of orders
How this looks in TypeScript today
Playground
An Order type with an option paymentId does not improve safety
undefined and ?: syntax
{ prop: undefined } requires prop to be present
{ prop?: 'foo' } allows prop to be absent, or set to undefined
This suggests some concept of absent already exists in TS.
Therefore, ?: T would equate to T | undefined | absent
absent
variablesconst foo: absent would probably not be allowed. const foo in javascript creates the variable, so this would be an oxymoron. This could be treated as a developers aid, being allowed, and emitting no javascript, but I don't see a use case, so its easier not to.
absent properties on the global/window object should I think prevent those variables being declared with var. I expect that this will very rarely be used, but it should really be enacted for consistency.
β Relations to other issues
Exact types #12936 - this issue solves some of the same issues that Exact types could be used to solve. Especially in the second example. However in the first example it is serving a different role.
Exact types might rely on
absent
type, or might be user implementable using anabsent
type.π οΈ Implementation challenges
I don't see anything super difficult. Unions and the like seem fine. Someone who knows more about TS internals can comment.
I'd be interested to hear how this type and concept would gel with existing code & infrastructure - whether it's something that would fit in naturally or not.
The text was updated successfully, but these errors were encountered: