-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Boolean function inference has incorrect behavior #51184
Comments
This is correct behavior. |
Yes, type SomeInferredFunctionType = (() => true) | (() => false);
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
type ExtractFnInfo<T> = T extends (arg: infer A) => infer V ? { arg: A; val: V } : never;
type ExtractArg<U> = U extends { arg: infer A } ? A : never;
type ExtractRet<U> = U extends { val: infer A } ? A : never;
type SimplifyFn<T extends Function> = (
arg: Simplify<UnionToIntersection<ExtractArg<ExtractFnInfo<T>>>>,
) => ExtractRet<ExtractFnInfo<T>>;
//Happy :)
const test: SimplifyFn<SomeInferredFunctionType> = () => {
return Math.random() > 0;
}; However, the above is not an option for me since I do not have direct access to |
That's only true if you assume the functions in question are mathematically pure (i.e. that the effects of calling the function are not observable beyond the return value, and depend only on the value(s) received as input). Incidentally |
Why? Naively, the provided function does not satisfy any constituent of the union. I don't know what logic you're using here to make this reduction step -- it's plainly wrong in the presence of nonpure functions, as noted by fatcerberus just now. |
Can you give me a concrete example of real life functions of type As a general rule, I'm not proposing that we collapse types like that, but I'm just saying I can't think of any specific use case where this specific function types shouldn't be equivalent. |
If I say I accept |
I could 100% envision code that depends on that invariant too. |
Yes, that's clearly what the Typescript compiler is doing internally. But you're using the self-contained logic of set notation here. And maybe I'm wrong, but to me it seems that smart outside observer logic should be prioritized over set logic. And I would argue that a smart outside observer (who doesn't have the equivalent of a phd in set theory like TS core devs) would think those two types should be equivalent. Also concrete example would be great! Maybe I just need to see a real life example for what you're saying to make more sense to me. |
Do you think that for all |
"If you call the function twice you get the same type" seems like an invariant that real code could easily depend on (there's almost certainly already code that does), no deferral to set theory necessary. |
No, those definitely should not be equivalent. It's just in this boolean case. You can see it in the comment mentioned issue but the problem primarily arises from the fact that And that's a good example @fatcerberus I hadn't considered. With the conditional type, in theory you should be able to do type narrowing (although currently the ts compiler isn't smart enough to do so). Maybe the real issue is the convoluted inference process that is generating this conditional type and I should isolate that issue and write up a bug report. |
Why is |
@RyanCavanaugh I assume the relevant distinction is unit type vs. not. And |
It's honestly a little unfortunate that we made |
I'm doing some complex type inference stuff and deep down in the chain of logic, this is the core offending logic:
I then use the inferred types to generate an inferred function type. |
Well, I think I'll just close this. There's no good way to solve this issue without likely causing more problems than it solves. Maybe one day on a major TS bump it might make sense to explore the ramifications of making type DoSomething<T> = T extends true
? DoAnotherThing<boolean>
: T extends false
? DoAnotherThing<boolean>
: DoAnotherThing<T>; |
@scottmas You know you can just disable the distributive behavior, right? type Foo<T> = [T] extends [boolean] ? T[] : unknown[];
type Test = Foo<boolean>; // boolean[] - NOT true[] | false[] |
Ahh yes, thank you |
Bug Report
Typescript is unable to correctly determine that a function's return satisfies the specified type.
🔎 Search Terms
"Typescript expanding boolean into true/false" (#30029)
🕗 Version & Regression Information
v. 4.7.4
⏯ Playground Link
https://www.typescriptlang.org/play?ts=4.9.0-dev.20221014#code/LAKALgngDgpgBAIQIYBMBiA7OBeOAKPAShwD44wAnAVxmIB98jS4AzJAGwGdaBuUAYwD2GTmHIxRALkSpMOfMWxkA3qDhwKMMFQpYAskjAALAHQUkGFIIC2TMgAY+IAL48gA
💻 Code
🙁 Actual behavior
Typescript thinks
test
does not satisfy its given type when it does. Note, that it is possible (with much difficulty) to collapseSomeInferredFunctionType
into the correct simplified type() => boolean
when I have direct access to the type. However, in my codebase, the type is actually automatically inferred and I do not have access to it and I am just consuming it.🙂 Expected behavior
Typescript should be able to determine that
test
satisfies the condition given bySomeInferredFunctionType
The text was updated successfully, but these errors were encountered: