Fix: is-equal fails in some cases when intersecting wrapped types (F<[A]> & G<[A]>)#1231
Conversation
…qual<F<[Arg]> & G<[Arg]>, never> : never)
…Arg ? IsEqual<F<[Arg]> & G<[Arg]>, never> : never)
|
Thanks for contributing. |
|
AI review:
|
|
Thank you.
Regarding this, I have added several test cases.
Regarding this comment, only the test case PSDuring the exploration of the export type IsEqual<A, B> =
_NonNeverId<A> extends UnknownArray
? _NonNeverId<B> extends UnknownArray
? _IsEqual<IsTuple<_NonNeverId<A>>, IsTuple<_NonNeverId<B>>> extends true
? A extends [infer HeadA, ...infer TailA]
? B extends [infer HeadB, ...infer TailB]
? IsEqual<HeadA, HeadB> extends true
? IsEqual<TailA, TailB>
: false
: _IsEqual<A, B>
: _IsEqual<A, B>
: _IsEqual<A, B>
: _IsEqual<A, B>
: _IsEqual<A, B>;With this version, const notEqualTupleUnionAndTuple: IsEqual<[0, 1] | [0, 2], [0,2]> = false
expectType<false>(notEqualTupleUnionAndTuple);const notEqualTupleUnionAndTuple: IsEqual<[0, 1] | [0, 2], [0,2]> = true
expectType<true>(notEqualTupleUnionAndTuple);If this phenomenon is not my oversight, should I open a separate issue and discuss it there? (Translated by AI) |
|
@taiyakihitotsu Even the following solution seems to pass all the existing test cases: export type IsEqual<A, B> =
[A] extends [infer A]
? [B] extends [infer B]
? _IsEqual<A, B>
: false
: false;
type _IsEqual<A, B> =
(<G>() => G extends A & G | G ? 1 : 2) extends
(<G>() => G extends B & G | G ? 1 : 2)
? true
: false;So, either we can simplify the existing solution or we need to add tests that fail with the above solution. |
|
@som-sm I'll add other baseline test cases as well, just to be safe. I think it's probably fine. If I decide to adopt your code, should I just leave a comment to mention that? Let me know if there's a preferred way to reflect your suggestion properly — I just want to make sure it doesn’t look like I’m claiming credit for it. |
No attribution required, feel free to just copy paste the code. If you really want to, you can add co-authors in your commits. |
|
Understood. |
If you're planning to add me as a co-author, then you'd need my email. Use: |
Co-authored-by: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com>
|
I found and added those three test cases, along with one simple baseline case ( These tests demonstrate that @som-sm's definition is correct. type Greet = {
(fullName: string): string;
(firstName: string, lastName: string): string;
};
type InferGreet = Greet extends infer F ? F extends (0 extends 0 ? F : 1) ? [F] : 2 : 3
type IIexpectTrueIntersectionGreet = IIIsEqual<[Greet] & [Greet], InferGreet>// false
type expectTrueIntersectionGreet = IsEqual<[Greet] & [Greet], InferGreet> // true
type StringArrayReturn = {
(s: string): string[];
(ss: string[]): string[];
};
type InferStringArrayReturn = StringArrayReturn extends infer F ? F extends (0 extends 0 ? F : 1) ? [F] : 2 : 3
type IIexpectTrueIntersectionStringArrayReturn = IIIsEqual<[StringArrayReturn] & [StringArrayReturn], InferStringArrayReturn> // false
type expectTrueIntersectionStringArrayReturn = IsEqual<[StringArrayReturn] & [StringArrayReturn], InferStringArrayReturn> // true
type SomeTypeReturn = {
<T>(value: T): T;
<T>(value: T[]): T[];
};
type InferSomeTypeReturn = SomeTypeReturn extends infer F ? F extends (0 extends 0 ? F : 1) ? [F] : 2 : 3
type IIexpectTrueIntersectionSomeTypeReturn = IIIsEqual<[SomeTypeReturn] & [SomeTypeReturn], InferSomeTypeReturn> // false
type expectTrueIntersectionSomeTypeReturn = IsEqual<[SomeTypeReturn] & [SomeTypeReturn], InferSomeTypeReturn> // true(This may be due to the fact that intersection types like I also added @som-sm as a co-author. Please take a look. |
I had completely overlooked the type narrowing problem. The following remark in your review was exactly about this issue, it seems.
Here is the branch where I was testing the failing definition: https://github.com/taiyakihitotsu/type-fest/tree/fix/equal-tuple-case-wip-false |
@taiyakihitotsu I'm still not sure if those test cases are required. If not, let's remove them. If they are required, can you tell me with which implementation of Everything else LGTM! |
|
Thank you, and apologies for the lack of explanation earlier.
I've kept the three test cases that were previously passed even by the initial definition as a precaution because they could fail with some alternative definitions. |
|
@taiyakihitotsu Ok, gotcha, thanks for the clarification.
Yeah, that's fine!
In that case, I don't think this is related to function overloading, looks like the test could be simplified to this: // [T] & [T] is not simplified
declare const tupleIntersection: IsEqual<[{a: 1}] & [{a: 1}], [{a: 1}]>; // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents
expectType<true>(tupleIntersection);Even this fails with the earlier proposed implementation. Also, I think having just one test for this should be fine, right? |
Thanks, I didn’t know that.
If the test can be sufficiently covered using just intersection, I think that should be fine too. |
Co-authored-by: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com>
|
I pushed this change. #1231 (comment) |
Hello.
This PR fixes an edge case of
is-equal.d.ts:(Sorry if I've made any mistakes with the PR or coding, and please forgive me if my English isn't very good...)
Assuming
FandGreturn['something', true]if theextendscondition is matched, or['something', false]if not.If we want to intersect thir return types
(F<A> & G<A>)and expect to getneverso thatIsEqual<It, never>evaluates totrue, but it instead returnsfalsewhenAis wraped as[A]and the right side of theextendsin bothFandGis also wrapped.This is the case I want to fix.
This case occurs, as shown below, when a type variable is introduced via infer.
A concrete example is easier to understand, so I will provide an overview below.
Thanks in advance!