-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Return type incorrectly inferred as void #57840
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
Comments
Interestingly if the function is async now |
It's related to this 5.1 change. The linked PR mentions that:
I'm not sure what's the exact reason behind this but I find it confusing and inconsistent too. I get it that it might be easy to forget a return value in such cases. It feels like a potential lint rule for people who prefer explicit This is a reason why people write weird things when they actually want to allow those implicit undefined when the conceptual return type is meant to be a union that includes declare const UNDEFINED_VOID_ONLY: unique symbol;
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
type EffectCallback = () => void | Destructor; Whereas if type Destructor = () => void;
function useEffect(cb: () => undefined | Destructor) {}
useEffect(() => {
// it would be perfect if this could be implied
return undefined;
});
// errors as expected
useEffect(() => {
return 10;
});
useEffect(() => {
// it works just fine
return () => {};
});
useEffect(() => {
return () => {
// we don't care about the return type here so it's allowed
return "foo";
};
}); |
This is just a bug. We have special logic in contextual typing to ensure that a function that would normally be inferred as type FN = () => undefined;
// Not an error
const fn1: FN = () => {
}; Something with the union with Promise is messing it up, probably because we also have logic to make the Promise case in specific also work: type FN = () => Promise<undefined>;
// Also not an error
const fn1: FN = async () => {
}; Unions with other values don't seem to have this problem |
Maybe we are talking about some other behaviors. I think the OP is reporting the same problem as the one showcased here: type FN = () => string | undefined;
// Type '() => void' is not assignable to type 'FN'.
// Type 'void' is not assignable to type 'string | undefined'.(2322)
const fn1: FN = () => {
return;
};
// ok
const fn2: FN = () => {
return undefined;
};
// Not all code paths return a value.(7030)
const fn3: FN = () => {
if (Math.random()) {
return "";
}
}; The code that "converts" the inferred Unless you are saying that both of those should be errors: type FN = () => Promise<undefined> | undefined;
// Type '() => void' is not assignable to type 'FN'.
// Type 'void' is not assignable to type 'Promise<undefined> | undefined'.(2322)
const fn1: FN = () => {
return;
};
// ok
const fn2: FN = async () => {}; IIUC, this is not what OP's post is about though so the added "Bug"/"Help Wanted" labels make it confusing to me. |
What I mean is that if the contextual type itself i a union with other types, that doesn't spoil the pot: type FN = 42 | (() => undefined);
// Not an error
const fn1: FN = () => {
};
To clarify, this code should not have an error: type FN = () => Promise<undefined> | undefined;
const fn1: FN = () => {
return;
}; Seems like using |
π Search Terms
π Version & Regression Information
β― Playground Link
https://www.typescriptlang.org/play?ts=5.5.0-dev.20240318#code/FAFwngDgpgBAYgORgXhgCgJQoHwwAoBOA9gLYCWAzlADwCuAdgCZQBmZ9UjuAPjA82w6MA3MGABjIvQogYLegEYAXPCSpMOGAG9gMPTAJQQtAvVEBfURKky59AEwrEKdFmS4d+g0ZP0+TVnZOC1EgA
π» Code
π Actual behavior
fn1
is detected as returningvoid
, whilefn2
is detected as returningundefined
.π Expected behavior
I'm not sure it makes sense to me that the inferred type for these kinds of function is
void
rather thanundefined
, I would expect the more precise type to be inferred in this case.Mainly there's no difference in JS between doing a
return;
and areturn undefined;
, unless.toString()
is called on the function containing that, so TS differentiating betweenreturn;
andreturn undefined;
seems a bit weird to me.Presumably this is something that has been considered and returning
void
is done on purpose and it's actually better on average, and requiring an explicitreturn undefined;
makes sense, but I'd like to point out that there are some detectable cases where one is notreturn
-ing to skip the following code, but one is explicitly returningundefined
for no other reason than to be explicit about it, for example in this case, where the return statement is the last statement of the function, and it's not even inside anelse
branch. Like there is no other purpose to thatreturn
than to mean, in every way, what the longerreturn undefined;
expresses, so perhaps the logic that decides whethervoid
orundefined
should be the returned type could be refined to account for this specific scenario.Also if I'm passing a function like that as an argument that accepts
() => undefined
I feel like that should be taken into consideration, and the function in that case should "obviously" be considered as returningundefined
rather thanvoid
.Additional information about the issue
I stumbled on this issue because at one point I'm accepting a function that has the following interface:
() => (Promise<undefined> | undefined)
, the idea is that if the function returns a promise I'll call itsthen
function, and if it doesn't I'll call thethen
function on a fallback value, so I'll just end up calling something calledthen
either way. Example code:The text was updated successfully, but these errors were encountered: