microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.04k stars 12.49k forks source link

Type constraint not checked when a type parameter from a function is passed to a mapped type #49302

Open JaredRohe opened 2 years ago

JaredRohe commented 2 years ago

Bug Report

🔎 Search Terms

mapped types generic type constraint lost when passed from function

🕗 Version & Regression Information

⏯ Playground Link

Playground link with relevant code

💻 Code

type SomeMappedType<T extends Record<string, any>> = {
  [K in keyof T]: T[K]
}

function myFunc<T extends SomeMappedType<T>>(myArg: T){

}

const y: SomeMappedType<object> = {foo: 'bar'}; // okay.  makes sense.

//@ts-expect-error
const x: SomeMappedType<string> = {foo: 'bar'}; // This error makes sense. `string` type parameter violates the Record<string, any> constraint

myFunc<string>('hey');  // Why is `string` not checked against the `Record<string, any>` constraint in this context ??

🙁 Actual behavior

No error when I pass in string as a type parameter to myFunc i.e myFunc<string>.

🙂 Expected behavior

Typescript errors and warns me that string does not satisfy the constraint of Record<string, any> when I attempt to make the following function call: myFunc<string>('hey');

RyanCavanaugh commented 2 years ago

During the call we instantiate SomeMappedType<T> with a special type, let's call it *, that allows us to validate T even though it's appearing in a recursive position. What's supposed to come out of that type is a type which is assignable from the instantiated T and no other types, but since SomeMappedType is homomorphic, if a primitive (string) goes in, then that same primitive (string) goes out, so this actually ends up succeeding even though string isn't a Record<string, any>.

It's not obvious how this can be fixed but anyone's welcomed to try.

JaredRohe commented 2 years ago

@RyanCavanaugh thanks so much for the response! I see, that makes sense. I wonder if it would be feasible to at least just display a compiler warning explaining that the type constraints in this context will not have any effect?

RyanCavanaugh commented 2 years ago

I don't think there's anything that cleanly distinguishes the problematic cases from the non-problematic cases. This behavior has been in place for approximately forever without anyone noticing so I don't think we'd want to warn on that code regardless - it's clearly an edge case.

fatcerberus commented 2 years ago

this actually ends up succeeding even though string isn't a Record<string, any>.

I would have actually guessed the opposite (that string is a valid instantiation), for the same reason that string is assignable to {}.

Josh-Cena commented 2 years ago

{} does not require index signatures, but Record<string, any> requires an index signature (?)

type A = string extends Record<string, any> ? true : false;
fatcerberus commented 2 years ago

Cursed?

Let it never be said that the TS team doesn't have a sense of humor. 😄