Skip to content

Add constraints to type parameters of Has #14

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

Open
younho9 opened this issue Sep 21, 2021 · 2 comments
Open

Add constraints to type parameters of Has #14

younho9 opened this issue Sep 21, 2021 · 2 comments

Comments

@younho9
Copy link

younho9 commented Sep 21, 2021

Because typescript doesn't support strict version of Extract for union types. (microsoft/TypeScript#31474)

How about add constraints to type parameters of Has?

current behavior

assert<Has<'a' | 'b', 'a'>>(true); // true.
assert<Has<'a', 'a' | 'b'>>(true); // also true. Can 'a' has 'a' | 'b' ?

assert<Has<string | number, number>>(true); // true.
assert<Has<number, string | number>>(true); // also true. Can `number` has `string` | `number` ?

Suggestion

/**
 * Checks if type `T` has the specified type `U`.
 */
export declare type Has<T, U extends T> = IsAny<T> extends true ? true : IsAny<U> extends true ? false : Extract<T, U> extends never ? false : true;
assert<Has<'a' | 'b', 'a'>>(true); // true.
assert<Has<'a', 'a' | 'b'>>(true); // Type '"a" | "b"' does not satisfy the constraint '"a"'.

assert<Has<string | number, number>>(true); // true.
assert<Has<number, string | number>>(true); // Type 'string | number' does not satisfy the constraint 'number'
@kevinpeno
Copy link

kevinpeno commented Mar 10, 2022

@younho9 have you taken a look at the type definition of Has? First argument is the test subject. Second argument is the assertion. Perhaps a more concrete example would help here? Say I have a global type of all possible languages and a check type/function (like a type guard) that narrows to a specific one. Should this not be truthy?

const allowedLocales = [
  'us-en',
  'ca-en',
  'ca-fr',
] as const

function isAcceptedLocale<T extends typeof allowedLocales[number]>(locale:T): locale is T {
  return allowedLocales.includes(locale)
}

const myLocale = 'us-en'
if(isAcceptedLocale(myLocale)) {
  assert<Has<typeof myLocale, typeof allowedLocales[number]>>(true)
}
else {
  assert(true)
}

Based on your first example I think it should be true, because I'm trying to assert the typeguard will respond to the expected structure.

However, based on your second example, I can see the need for more strictness given the typical testing structure of expect(result).is.a.(value). The problem with example 2 is that while it works for Assert, which has the types pre-specified in the definition, TS's ability to contextualize beyond the current scope (type/function definition) is pretty limited. See this example where I update Has to more appropriately ensure T extends U (cascade effect to other functions possibly creating a maintenance nightmare because the T extends U effect must permeate every single definition). However, assuming this lib dealt with that overhead it would get the expected result (1 fails, wrong order; 2 passes correct order)

Not saying there's not a solution, but it might need an entire rewrite of the lib, if it is possible at all.

@kevinpeno
Copy link

kevinpeno commented Mar 10, 2022

Whelp. I played around a little more and looked at more examples in the current lib (and other issue comments). I think I've found a solution to ensure the interface of Has matches what one would expect a typical testing interface would look like.

result

If we do this I noticed a few other functions seem to not really follow the "result === expectation" (left to right) thinking, so a PR should include tweaks to their direction as well. Not to mention this would most certainly be a breaking change (major bump)

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