Closed ultd closed 3 years ago
Control flow narrowing works based on looking for syntax that applies to the variable in question. In this example, nothing directly seems to influence value
, so it has its unnarrowed type.
We don't have the performance budget to do the sort of whole-universe counterfactual analysis that would be required to correctly detect this pattern.
But consider this code:
type EitherOrNull<T extends object, S extends object> = T & { [K in keyof S]: null } | S & { [K in keyof T]: null }
function someFunc(): EitherOrNull<{ error: Error }, { value: number }> {
// some functionality..
return { error: new Error('nope'), value: null }
// or `return { error: null, value: 33 }`
}
const anotherFunc = (): Error | null => {
const result = someFunc()
if(result.error){
return new Error('something went wrong!')
}
const numberStr1 = result.value.toString()
// ^ but this works?
return null
}
Being that this example works and the only difference is the destructuring of the object returned, it makes sense that the type inferred would be passed to the newly declared const variable.
Would there need to be anymore computing being that the types are already being inferred by the TS compiler?
Looks similar to #39026 which considered duplicate of #12184
No progress made on that duplicate in 4 years. Any resources you could point me to for contributing? Any document explaining the repo would be really helpful. It seems this will not be taken care of unless a PR is made by myself or someone else that's having this specific problem.
Reasoning:
Consider this piece of Go code:
func someFunc() (error, int) {
// ... some functionality
return errors.New("nope"), 0
// or return nil, 33
}
func anotherFunc() error {
err, value := someFunc()
if err != nil {
return fmt.Errorf("Something went wrong!")
}
valueStr := strconv.Itoa(value)
return nil
}
I know TS is not Go and Go isn't TS but we should be able to return some form of typed tuple that the compiler can infer types of using control flow analysis. Destructuring objects allows for a type of "tuple" but unfortunately lacks the compiler's ability to infer the types.
What about Tuple type like this:
export type EitherOrNullTuple<T, S> = [T, null] | [null, S]
function someFunc(): EitherOrNullTuple<Error, number> {
// some functionality..
return [new Error('nope'), null]
// or `return [null, 33]`
}
const anotherFunc = (): Error | null => {
const [error, value] = someFunc()
if(error){
return new Error('something went wrong!')
}
const numberStr1 = value.toString()
// same Error: Object is possibly 'null'.ts(2531)
return null
}
This still exhibits the same behavior as the previous example (TS-Playground). Destructuring the TS typed tuple doesn't allow for control flow analysis. This is a bug in my view being that this is a built in TS type.
Why not just use try-catch blocks? Try-catch blocks are a pain to work with and the idea of writing code that can throw an error in one place and could be caught (and handled) elsewhere is not a good pattern to me (I understand this is debatable and to each his/her own).
We should try to improve a language by learning from others and this is a good pattern TS should adopt or should at least support.
No progress made on that duplicate in 4 years. Any resources you could point me to for contributing? Any document explaining the repo would be really helpful. It seems this will not be taken care of unless a PR is made by myself or someone else that's having this specific problem.
You can start by reading CONTRIBUTING.md, and there are hundreds of PRs affecting control flow you can look at to see how changes there work. Looking forward to your PR!
TypeScript Version: 4.1.2
Search Terms: non-null, assertion, automatic, inferring
Code
EitherOrNull
type ensures that the return type ofsomeFunc
is eitherT & { [K in keyof S]: null }
orS & { [K in keyof T]: null }
. The TS compiler will not allow return type ofT & S
or{ [K in keyof T]: null } & { [K in keyof S]: null }
Comments such as "just use non-null assertion operator" is not helpful as the type returned from this function is used in other code downstream therefore the return type is important to work properly.
Expected behavior: I expect the TS compiler to know that
value
is not null because iferror
was not null, theanotherFunc
function would've returned the error and code execution would've ceased and therefore the code which usesvalue
would be unreachable. It's only aftererror
is deemed null by code is when the usage ofvalue
occurs at which point the TS compiler should know that thevalue
property is not null.Actual behavior: The TS compiler gives an error stating that
value
"is possibly 'null'" even though it's impossible as the return type ofsomeFunc
function ensures that it wouldn't be being thaterror
is null. The TS compiler is making me use non-null assertion operator or checking ifvalue
is not null using anif
statement.Playground Link: TS-Playground
Related Issues: None