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

Generic Conditional Type resolved to any when passed an error any type #59981

Closed
Eazash opened this issue Sep 16, 2024 · 8 comments
Closed

Generic Conditional Type resolved to any when passed an error any type #59981

Eazash opened this issue Sep 16, 2024 · 8 comments
Labels
Not a Defect This behavior is one of several equally-correct options

Comments

@Eazash
Copy link

Eazash commented Sep 16, 2024

πŸ”Ž Search Terms

"generic", "conditional type", "is any", "implicit any", "error", "module has no export member"

πŸ•— Version & Regression Information

  • This changed between versions 5.4.5 and 5.5.4

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.5.4#code/C4TwDgpgBAkgzgQQHYgDwBUB8UC8UAMUEAHsBEgCZxQCMUAZFOlAPxTABOArtAFxQAzAIYAbOBABQEihADGIoR2gBbAPYUuI6ACI4q5RAC0ajVu1QA3gF8JoSLGVgRAS1nPgyELnbgIqgVDOjqocwAAUuvpGJpoQ2gCUAHQywprAUnbQAEoQcGne8J6oMI4ubh4omBIA9NVQ9Q0AeiwZvlAAosSQsmQUOXkiwAWIKKhClTV19c1AA

πŸ’» Code

type IsAny<T> = 0 extends 1 & T ? true : false

declare module "some-module" {}
type ImplicitAny = typeof import("some-module").default

type Result = IsAny<ImplicitAny> // any

type ExpectedResult = IsAny<any> // true
type ExpectedResult2 = IsAny<{}> // false

πŸ™ Actual behavior

The conditional type resolves to any

πŸ™‚ Expected behavior

The conditional type should resolve to either TrueType or FalseType. In this specific example, it should resolve to true

Additional information about the issue

While I was unable to use every-ts, after manually bisecting I've foudn that the first release with this buggy behaviour is 5.5.0-dev.20240524

@Eazash
Copy link
Author

Eazash commented Sep 16, 2024

Related to #58960, possible cause outlined here (#58960 (comment))

@Eazash Eazash changed the title Generic Conditional Type Returns any when generic is an implicit any from typescript error Generic Conditional Type resolved to any when passed an error any type Sep 16, 2024
@lucaimbalzano
Copy link

The problem stems from how TypeScript infers types from modules. If "some-module" exports a default value with any (but the type isn’t explicitly declared), TypeScript might not treat it exactly the same as any.

To resolve this, you should explicitly declare the default export as any in your module.
Here's how:

  1. Explicitly Declare the Module's Default Export as any:
declare module "some-module" {
  const _default: any;
  export default _default;
}
  1. Type Checking with IsAny: Now, when you check IsAny, it should correctly detect the any type:
type IsAny<T> = 0 extends 1 & T ? true : false;

type ImplicitAny = typeof import("some-module").default;

type Result = IsAny<ImplicitAny>; // true
type ExpectedResult = IsAny<any>; // true

⏯ Here's you can check by ts live-code :

// Declare the module "some-module" with an explicit 'any' type for the default export
declare module "some-module" {
  const _default: any;
  export default _default;
}

// A utility type to check if...

Playground Link

@Andarist
Copy link
Contributor

@Eazash you have already found the other issue about it, this is no different from it. I think it would be best to close this issue to keep the discussion focused in the original thread about this change.

@Eazash
Copy link
Author

Eazash commented Sep 16, 2024

@Andarist In my opinion, there is a difference. The other issue is referring to the "Unresolved any" when a type doesn't exist, and how that exhibits different behavior. This issue is describing how an error any behaves differently from a regular any. Unlike unresolved there is no label/comment on hover, yet it breaks the contract of a conditional type. A conditional type by definition should return from either the true or false branch. By returning any it is quietly introducing a third branch.

@Andarist
Copy link
Contributor

Andarist commented Sep 16, 2024

Both are just the same for the compiler. It still feels to me that you could move this discussion to the other issue and try to advocate there that your use case is different than the one presented there (IMHO it's still the same but that's beyond this argument :P).

As Ryan said there, once you have an error "all bets are off" πŸ˜‰

A conditional type by definition should return from either the true or false branch.

This isn't technically true. Sometimes it returns both :D

type Test = any extends string ? 1 : 2
//   ^? type Test = 1 | 2

EDIT:// I now realized the other issue is closed, so I'll give you that. I think it doesn't make sense to comment much on closed issues so perhaps a new one is appropriate here. I anticipate it will be closed with the same conclusion though.

@Eazash
Copy link
Author

Eazash commented Sep 16, 2024

@lucaimbalzano The playground I linked to is a simplified version of a downstream bug (see nuxt/nuxt#29021). The intention was to show that the implicit any created from an "No export error" changes the behaviour of the conditional type.

If "some-module" exports a default value with any (but the type isn’t explicitly declared), TypeScript might not treat it exactly the same as any.

In this scenario, "some-module" does not have default exports. Also "some-module" would be any number of third-party modules, explicitly defining any is not an option

@Andarist

This isn't technically true. Sometimes it returns both :D

Thank you, I stand corrected. However, the bug still persists for a conditional like type IsTwo<T> = T extends 2 ? true : false and IsTwo<ImplicitAny> is supposed to be a boolean but becomes any (see this playgroud)

@RyanCavanaugh
Copy link
Member

The propagating behavior of the error any is intentional, so that when you have one type error, it doesn't result in (potentially) tens of thousands of downstream errors.

You should write this instead if you'd prefer the other behavior

type PossibleDefault<T> = T extends { default: infer U } ? U : any;
type ImplicitAny = PossibleDefault<typeof import("some-module")>;

@RyanCavanaugh RyanCavanaugh added the Not a Defect This behavior is one of several equally-correct options label Sep 16, 2024
@typescript-bot
Copy link
Collaborator

This issue has been marked as "Not a Defect" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Sep 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Not a Defect This behavior is one of several equally-correct options
Projects
None yet
Development

No branches or pull requests

5 participants