Skip to content
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

Narrowing empty string or false in generic misbehaves #58232

Open
ts2do opened this issue Apr 17, 2024 · 2 comments
Open

Narrowing empty string or false in generic misbehaves #58232

ts2do opened this issue Apr 17, 2024 · 2 comments

Comments

@ts2do
Copy link

ts2do commented Apr 17, 2024

πŸ”Ž Search Terms

generic, narrowing, empty string, false, falsy

πŸ•— Version & Regression Information

This is the behavior in every version I tried, but it seems to behave a little worse with TS v4.2.3 and prior.

⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.5.0-dev.20240417#code/GYVwdgxgLglg9mABKSBGAPAFUQUwB5Q5gAmAzomCALYBGOATogD4UgA2bzi4xOwMYHMS7AAhm1I4uAcmkA+ABR4AXK1oMumAJSIA3gCgAkPRxQQ9JHmYsefAUIDc+gL76U0eEncAmLLgJEZGp0jCyUHFy2-ILCLGISUiyyiirBGizaekYmZhaIVgD8+YhyiAAMiEVWqgC01flOru6wCMjgEADMfviEJOSU6qGsETYkdjEi4pIy8kqqAyGaOgbGpuaWlYgAsqJQABYAdKI0pEo6qniN+voQCKRQ+fPUi6O80UKIALxtaAqyWk5bmB7ogAJ5fH4QbwKGodAE3O4PABeEPcHRhcIcQA

πŸ’» Code

function func1<T extends number | null | undefined | false | ''>(x: number | T) {
	return x || undefined;
}
function func2<T extends number | null | undefined | false | ''>(x: number | T) {
	return x ? x > 0 ? x : -x : x;
}
function func3<T extends number | null | undefined | false | ''>(x: number | T) {
	return x ? Math.abs(x) : x;
}

const x: number | undefined = func1('');
const y = func2(-3);
const z = func3(-3);

πŸ™ Actual behavior

The inferred return type of func1 can contain false or '' depending on T. It's wrong because x || undefined can never result in false or '' and thus should not be in the resulting type.

func2 fails type checking: Operator '>' cannot be applied to types 'number | NonNullable<T>' and 'number'.. It's wrong because the truthiness check should narrow x to number. (Note that func3 succeeds type checking because the signature of Math.abs seems to provide supplemental information which narrows x to number.)

πŸ™‚ Expected behavior

The inferred return type of func1 is should number | undefined, regardless of T.

func2 is expected to pass type checking, x having been narrowed to number with the truthiness check.

Additional information about the issue

All functions act 'correctly' if defined without the use of generics, but it's inadequate because the return type of func2 and func3 would always be number | null | undefined | false | '' regardless of argument type.

My actual use case is creating functions that transform numeric inputs and propagate falsy values. I've worked around this issue using type assertions in spots where narrowing misbehaves.

@RyanCavanaugh
Copy link
Member

TS doesn't have any type-level representation of "The truthy values of T" or "All truthy values", so at any point where we have to write down the type, the next-best thing is NonNullable<T> which is a sound but not-complete superset (it includes '' when it shouldn't) of that.

@ts2do
Copy link
Author

ts2do commented Apr 25, 2024

But it seems like it wouldn't be that troublesome to define types which extracts or excludes falsy types from a union, much like NonNullable excludes null and undefined and use those in such cases. And if T includes number, both branches would have to include number because there's no NaN type, but I think you get that part done with Extract and Exclude.

Of course I don't know what all would be involved in the change, so maybe it's a difficult ask.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants