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

Bug with intersection types #1166

Closed
christianTNG opened this issue Jul 16, 2024 · 5 comments
Closed

Bug with intersection types #1166

christianTNG opened this issue Jul 16, 2024 · 5 comments
Assignees
Labels
bug Something isn't working enhancement New feature or request good first issue Good for newcomers invalid This doesn't seem right wontfix This will not be worked on

Comments

@christianTNG
Copy link

Bug Report

First of all, thanks a lot for all the hard work on typia! This bug report revisits a variant of issue #856 about better support for intersection types.

We recently encountered the problem that typia does not correctly apply the is-validation when some specific intersections exist somewhere nested in the type to be checked.

💻 Example

Originally our problem concerned the intersection of arrays in some property of an object, but it turns out that even much simpler intersections are also affected. Thus, the following example with template strings shows a very simplified case and can be tested in typia's playground ⏯ here:

import typia from "typia";
 
type Art = `${string}art${string}`;
type Bar = `${string}bar${string}`;
 
type ArtBar = Art & Bar;
 
const testString: ArtBar = "bart";
 
const resultArt = typia.is<Art>(testString); // true
const resultBar = typia.is<Bar>(testString); // true
const result = typia.is<ArtBar>(testString); // false 🗲

📝 Problem circumstances

The different types in the intersection, i.e. Art and Bar in the example above, may have originally come from external sources such as an OpenAPI type export, and are typically not under our control, or we may not even know exactly what they contain. Thus, the ad hoc workarounds mentioned in the original issue #856 - such as using generics or Omit - will usually not work here. Moreover, setting the is-validation result to false in the above example clearly contradicts TypeScript logic, since "bart" is successfully assigned to testString with type ArtBar. Thus, we still have the impression that typia's behavior here is a bug and not "invalid" and not "wontfix" as mentioned in the original issue #856.

💡 Suggestion

Consequently, we would suggest as an improvement for typia that for intersection types (especially in cases without other complications like generics): try to somehow check each part of the intersection independently first and then combine the results with logical AND.

If this bug fix is not possible quickly, then it would be great if - as a first, maybe temporary fix - typia could somehow detect such problematic cases and then avoid assigning a boolean value to the is-validation result at all. Instead, maybe temporarily return undefined (or even throw an error), so that we typia-users know that we have to try to use other workarounds until this bug is fixed.

Such a temporary fix might also be necessary for the random-generator on intersection types, because this generator seems to be broken on these intersection types, like the ArtBar type from above: It returns a complex object which is clearly not a simple string:

image

Thank you very much already in advance. ❤️

@samchon samchon self-assigned this Jul 21, 2024
@samchon samchon added good first issue Good for newcomers wontfix This will not be worked on invalid This doesn't seem right labels Jul 21, 2024
@samchon
Copy link
Owner

samchon commented Jul 21, 2024

image

Your intersection combining two template literal types is nonsensible.

As you can see from the above image, the Art & Bar type can't assign none of both Art and Bar type, so that it is not a type of proper template literal type. In actually, TypeScript compiler API (tsc) parses the intersection not template literal type as a kind of object.

Therefore, I'll fix your issue by generating an exception nonsensible type in your case.

@samchon samchon added enhancement New feature or request bug Something isn't working labels Jul 21, 2024
samchon added a commit that referenced this issue Jul 21, 2024
Fix #1166: nonsensible intersection type case
@christianTNG
Copy link
Author

Thank you very much for your response and especially for adding an exception in problematic cases already, @samchon. This will help developers to earlier recognize this issue if they are affected.

However, I hope I can quickly resolve the small misunderstanding around nonsensible here: ArtBar is the type for all strings which contain the substrings art AND bar.

An example variable which can be assigned to this type is included in my original bug report namely bart but it could also be e.g. art-bar or foo_bar_art. Thus, the intersection is clearly not nonsensible but rather a standard usage of TypeScript.

Moreover, the ArtBar is just one example which is used for the bug report. Also other TypeScript-valid intersections fail, e.g.:

type StringArray = string[] & any[];
const testArray: StringArray = ["foo_bar_art"];

or

type StringArray = ("case1"|"case2")[] & ("case2"|"case3")[];
const testArray: StringArray = ["case2"];

As mentioned in the original bug report: sometimes developers cannot control the types which they have to use for the intesection, e.g. because the types originate from an OpenAPI type export. Thus, it's not possible to simplify these cases in the code before using typia.

Hence, it would be great if you could please reopen this issue - maybe for your backlog - and in a future version somehow replace the exceptions with a working validation. Thank you very much in advance!

@samchon
Copy link
Owner

samchon commented Jul 22, 2024

I think you're misunderstanding the TypeScript intersection type. Your example codes are actually nonsensible.

At first, rather than string[] & any[] type, Array<string & any> is proper type. Even in the TypeScript compiler API, it can't parse string[] & any[] type. Instead, if you change it to Array<string & any> type, it is considered as Array<any> type in both TypeScript compiler and typia.

The second case is the same. Array<("case1"|"case2")[] & ("case2"|"case3")> is proper type, and it is considered as never type in both TypeScript compiler and typia.

@samchon
Copy link
Owner

samchon commented Jul 22, 2024

Also, I repeat that, Art & Bar type is not a possible case, and TypeScript compiler API also considers it as nonsensible.

If the Art & Bar type is a working type as you imagine, the intersection variable must be compatible with assignment from art and bar variables. However, as you can see from the below image, none of them is possible.

image

@christianTNG
Copy link
Author

christianTNG commented Jul 22, 2024

Thanks for your response but I think we still have a major misunderstanding here, I guess we are mixing up intersection types with union types. Let me please explain my arguments again step by step with examples from the TypeScript Playground:

  1. The ArtBar = Art & Bar is an intersection type. It represents all strings which contain the strings "art" and "bar" as substrings at the same time. It is not a union type. There is no need that strings which contain only one of the two substrings can be assigned to ArtBar. Examples:
  • We can NOT assign the string "art" to the type ArtBar because the substring "bar" is missing.
  • We can NOT assign the string "bar" to the type ArtBar because the substring "art" is missing.
  • We CAN assign the string "art-bar" to the type ArtBar because it contains both strings "art" and "bar" as substrings. See this TypeScript playground example as proof.

Thus, Art & Bar is certainly "a possible case" and for sure a valid TypeScript type. We use these intersection constructions a lot in our codebase.

  1. string[] & any[] is a valid TypeScript type. See this TypeScript playground example as proof that a string array like ["test"] can be assigned to this type. I am not sure what you mean with "proper type" but at least the validity of this type is no problem.

  2. string[] & any[] does not resolve to Array<any> or any[]. See this TypeScript playground example as proof that e.g. a number array cannot be assigned to this type.

  3. You made a minor typo and actually wanted to propose the Array<("case1"|"case2") & ("case2"|"case3")> type. With the typo fixed, this type correctly resolves to "case2"[]. Only the variant with the typo resolves to never. See this TypeScript playground example. Hence, the type resolves to exactly the same as my originally mentioned ("case1"|"case2")[] & ("case2"|"case3")[] type.

In a nutshell, I hope I was able to convince you that these intersection types are valid TypeScript types and not "nonsensible" and should thus please be supported by Typia in the long run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request good first issue Good for newcomers invalid This doesn't seem right wontfix This will not be worked on
Projects
No open projects
Status: Done
Development

No branches or pull requests

2 participants