Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upGitHub is where the world builds software
Millions of developers and companies build, ship, and maintain their software on GitHub — the largest and most advanced development platform in the world.
Typeguard method not working for class members #36887
Comments
|
Could it work if there was a way to narrow the property directly instead of the isNumberFoo(): this["prop"] is number {
return typeof this.prop === "number";
} |
Could there be some syntax like
That lets the programmer explicitly set the "else" type? I guess if that existed, the compiler could just do it automatically:
|
I'm hitting this issue in a related situation where the generic type is a union:
|
I have come across this issue while trying to implement a Rust-like const type Ok<T> = { tag: 'ok', val: T }
const type Err<E> = { tag: 'err', err: E }
export class Result<T, E> {
private constructor(readonly repr: Ok<T> | Err<E>) { }
static Ok<T, E>(val: T): Result<T, E> { ... }
static Err<T, E>(val: E): Result<T, E> { ... }
isOk(): this is { readonly repr: Ok<T> } { ... }
isErr(): this is { readonly repr: Err<E> } { ... }
getVal(this: { readonly repr: Ok<T> }: T { ... }
getErr(this: { readonly repr: Err<E> }: E { ... }
// various adapters like map, mapErr, and, or, etc...
} This should allow writing code like so: type AppError = { reason: IoError | ... }
declare function read_file(path: string): Result<Buffer, IoError>
function do_something(): Result<void, AppError> {
const res = read_file(...)
if (res.isErr()) {
// narrowed to { repr: Err<E> }
return Result.Err({ reason: res.getErr() })
}
// else branch, narrowed to { repr: Ok<T> } <-- this does not occurr!
const file = res.getVal()
// ...
} I've tried a different approach defining export type Result<T, E> = (Ok<T> | Err<E>) & ResultMethods<T, E>
export interface ResultMethods<T, E> {
isOk(): this is Ok<T>,
isErr(): this is Err<E>,
// ...etc
} This feels unnecessarily convoluted though, because each result instance needs to have a bunch of arrow functions created along-side it each time it is constructed. |
A simple example of this problem: abstract class Color {
isRed(): this is Red { return false }
isBlue(): this is Blue { return false }
isGreen(): this is Green { return false}
}
class Red extends Color {
isRed(): this is Red { return true }
}
class Green extends Color {
isGreen(): this is Green { return true }
}
class Blue extends Color {
isBlue(): this is Blue { return true }
}
let c: Color = new Blue()
if(c.isRed()) {
c // c: Red
} else if(c.isGreen()) { // Property 'isGreen' does not exist on type 'never'
// c: never (Green expected)
} else if(c.isBlue()) { // Property 'isBlue' does not exist on type 'never'
// c: never (Blue expected)
}
c // c: Red (Blue expected. if all conditions false, then Color expected) |
TypeScript Version: 3.8-Beta (on Playground)
Search Terms: typeguard class member
Code
Expected behavior:
In the
if
branch,prop
is narrowed tonumber
. In theelse
branch,prop
is narrowed tonumber[]
.Actual behavior:
In the
if
branch,prop
is narrowed tonumber & (number | number[])
, which works but looks confusing.In the
else
branch,prop
is not narrowed at all.Playground Link: here
Related Issues: #26212 describes the exact same issue, but was closed without a proper solution.