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

Could be instantiated with a different subtype of constraint #50027

Closed
quantumsheep opened this issue Jul 24, 2022 · 3 comments
Closed

Could be instantiated with a different subtype of constraint #50027

quantumsheep opened this issue Jul 24, 2022 · 3 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@quantumsheep
Copy link

Bug Report

πŸ”Ž Search Terms

not assignable to the constraint of type
could be instantiated with a different subtype of constraint

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

type A = () => void;
type B = () => void;

// error
function f1<T extends A | B>(value: T): T {
  return () => value();
}

// ok
function f2<T extends A | B>(value: T): T extends A ? A : B {
  return () => value();
}

πŸ™ Actual behavior

The following error is happening with f1 but not f2:

Type '() => void' is not assignable to type 'T'.
  '() => void' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'B | A'.

πŸ™‚ Expected behavior

Defining f1 should work fine.

@jcalz
Copy link
Contributor

jcalz commented Jul 25, 2022

I don't see a bug here.

() => value() is assignable to both A and B and therefore assignable to T extends A ? A : B, but it is not necessarily assignable to T. T might be narrower than A | B; in particular it may have other properties:

function f() { }
f.prop = "hello";

Your f1() function claims to return the same type as it takes in, so the following code compiles with no error

f1(f).prop.toUpperCase(); // okay?!

but of course there's a runtime error, since f1(f) has no prop property. That's an indication that f1 was not implemented properly. In this case, T is instantiated with typeof f, which is a different subtype of A | B than () => void is.

On the other hand, f2() only claims to return either A or B, neither of which have a known prop property, so the equivalent call with f2(f) gives the expected compiler error:

f2(f).prop.toUpperCase(); // compiler error
//    ~~~~ <-- Property 'prop' does not exist on type 'A'

So f2 is behaving well, and f1 is in error for exactly the reason the compiler states.

Playground link to code

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jul 28, 2022
@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@acrabb
Copy link

acrabb commented Oct 4, 2022

I think I'm running into a similar issue and could use some help.

The goal is: accept a generic type of one of two options, but have one be a default. Return that type or undefined

I don't understand why T "could be instantiated with an arbitrary type" when I'm specifying that it could only be string or string[] (and assigning string if its not defined)

export default function useXParam<T extends (string | string[]) = string>(key: string): T | undefined {
  const nextRouter = useRouter()
  return nextRouter.query[key] // error   // return type of nextRouter.query[key] is: string | string[] | undefined

  // ERROR:
  // Type 'string | string[] | undefined' is not assignable to type 'T | undefined'.
  // Type 'string' is not assignable to type 'T'.
  // 'string' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'string | string[]'.ts(2322)
}

Its working as I intend from the caller perspective. (The developer can call useXParam() will behave like useXParam<string>() unless string[] is used.)
But inside the function, the return line is giving error TS 2322

Offroaders123 added a commit to Offroaders123/NBTify that referenced this issue Jun 8, 2023
I wish this was working, it would be really cool! It extends the similar behavior that the `NBTData` data does, where it attaches the format options types as a generic on the `NBTData` type itself. This would be neat to define the shape of the `NBTData` object fully, including it's content, and it's format options.

While writing that last part, I realized a better way to handle this, and may be generics on the `NBTData` object aren't the way to go. Since it's only the types that have to stay static, maybe it makes more sense to manage these from a level above the `NBTData` object. I would have liked to be able to constrain the values you pass into the `NBTData` object, by using generic constraints, but it seems like this prevents you from allowing the parameter to be optional.

I think I found some options to describe the error, I think they are related to the issue I'm having. The type issues are only occurring when the options parameter is applied with a default value, or set to anything other than the type of itself. Even with type casting to either `FormatOptions` or `U`, it won't let you assign anything to the parameter.

https://stackoverflow.com/questions/56505560/how-to-fix-ts2322-could-be-instantiated-with-a-different-subtype-of-constraint
microsoft/TypeScript#50027
https://www.reddit.com/r/typescript/comments/rncy44/implementing_function_types_with_a_generic/
https://news.ycombinator.com/item?id=30247357
https://stackoverflow.com/questions/67397978/ts2322-generic-not-propagated-correctly
microsoft/TypeScript#43820

After discussing that, I think that's probably the way to go. Maybe the `NBTData` object shouldn't be where things are dictated, hmm. It seems to work really nice with the `NBT.read()` and `NBT.write()` functions though, and I wish I didn't have to lose that functionality.

The next stage I may try for this is to make a generic for each option parameter. I don't really like the idea of that though. Let's see what happens. This is so close! I realized the `const U ...` const declaration for the generic works great here too, which makes the `NBTData` properties reflect the options you pass into the options object, with exact values. So passing in `"Level"` for the name option will reflect `"Level"`, rather than only `string`, which is what happens without using `const U`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

5 participants