I have an interface I
with only optional properties. In my case I
is a generic. I need a type narrowing function that still allows to delete
the narrowed property. The following code shows the problem.
interface I {
value?: number
}
function check(i: I): i is I & { value: number } {
return "value" in i
}
function test(i: I) {
if (check(i)) {
i.value += 1 // ok
delete i.value // error: The operand of a 'delete' operator must be optional.
}
}
// i as I is not an option to force deletion because it allows the access
// to i.value after deletion
function test2(i: I) {
if (check(i)) {
i.value += 1 // ok
delete (i as I).value // ok
i.value += 1 // !!! still ok
}
}
// expected behaviour should be something like
function test(v: I) {
if ("value" in v) {
v.value += 5 // ok
delete v.value // ok
v.value += 5 // error: understandable and good
}
}
The problem is, that the narrowing creates a new interface with value
as a required type. So I need syntax to say that value
can be optional / removed but currently contains a value.
A workaround would be to define a function for deleting that renarrows the value back to optional.
Is there some syntax or pattern to allow the use of the delete
keyword?
Unfortunately you can't express what you're doing with a custom type guard function whose return type is of the form i is X
for some type X
. TypeScript doesn't have a type that means "the value
property of type number
is optional (so it may be delete
d), but known to be present (so it can assigned to number
)". The type {value?: number}
means the property is optional but possibly missing, and the type {value: number}
means the property is known to be present but is required. Neither is appropriate for the return type of a custom type guard function.
The desired state is possible to achieve via control flow anlysis as you've seen (although I'd change your example to
function test(i: I) {
if (typeof i.value !== "undefined") {
i.value += 5 // ok
delete i.value // ok
i.value += 5 // error: understandable and good
}
}
instead of if ("value" in i)
). But again, there's no appropriate way to represent that post-checked state as giving i
a new type.
I didn't find a relevant TypeScript GitHub issue; so you might want to file a new feature request for a type that works this way. But I doubt it would be implemented, since it's not a very common situation, and the workaround is to use the direct type guard instead of a custom type guard function. If you do decide to file a feature request, you should take care to demonstrate why available workarounds don't suffice.