Open rsimoes opened 3 years ago
I'm not sure if/how this could be implemented without significant perf penalty -- property access doesn't currently create a type narrowing branch on the parent object, but we'd have to everywhere for this to work. Worth hearing feedback on, though. Never mind (see below)
Some property accesses already contribute to type narrowing:
interface A { isOwned: false }
interface B { isOwned: true, owner: object }
type C = A | B;
declare const x: C;
if (x.isOwned) {
x.owner;
}
But this only works when the type is a union. So maybe a type with such a property could be seen as a discriminating union.
Use case:
enum Mode {
Production = 'prod',
Development = 'dev',
// other modes - Staging, Testing, etc...
}
class Environment_v1 {
constructor (readonly mode: Mode) {}
get isProduction () /* I would like to add the type guard here. */ {
return this.mode === Mode.Production
}
get isDevelopment () /* I would like to add the type guard here. */ {
return this.mode === Mode.Development
}
// etc...
}
assert(new Environment_v1(Mode.Production).isProduction === true)
assert(new Environment_v1(Mode.Production).isDevelopment === false)
I would gladly change the getter isProduction
to instead be a method isProduction()
so that code that consumes this API would benefit from the type discrimination. However... all existing uses of env.isProduction
and env.isDevelopment
would now return truthy.
This would, of course, be a breaking change to the API. Nothing scary, just a major version bump... except that, should downstream forget to replace even one usage of the getter with a method call when upgrading to the new major version, all hell would break loose.
class Environment_v2 {
constructor (readonly mode: Mode) {}
/* not a getter anymore */ isProduction (): this is ProductionEnvironment {
return this.mode === Mode.Production
}
/* not a getter anymore */ isDevelopment (): this is DevelopmentEnvironment {
return this.mode === Mode.Development
}
}
assert(new Environment_v2(Mode.Production).isProduction() === true) // correct
assert(new Environment_v2(Mode.Production).isProduction === true) // accidentally correct
assert(new Environment_v2(Mode.Production).isDevelopment() === false) // correct
assert(new Environment_v2(Mode.Production).isDevelopment === true) // BOOM!
bump
I ran into this today, I'm surprised this isn't a thing already.
Fetching libs like TanstackQuery or RTKQuery expose properties like isFetching, isSuccess, isError which with typeguards can narrow the type to avoid unnecessary checks and usage of !.
Suggestion
π Search Terms
getter, predicate, type guard
β Viability Checklist
My suggestion meets these guidelines:
β Suggestion
This has been requested before (e.g., #6994) but declined because at the time getters were merely an aspect of implementation. Since that is no longer the case, would there be any interest in revisiting the suggestion?
π Motivating Example
π» Use Cases
I have a use case much like the above. Currently type narrowing by checking
thing.owner
isn't honored whenthing
is used as a function argument:With the above, I can create a normal function or method that returns a type predicate or otherwise simply assert that
thing
is anOwned<Thing>
. Given that user-defined type guards are encapsulated type assertions, and getters are shiny and neat, a boolean getter that doubles as a type guard would be nicest.