# Allow type parameter constraints to reference other type parameters (#2304) - Pre 1.0, used to be that we could allow you to write something like `class Foo<T extends X, U extends T>` - Had too many issues with infinitely diving in to types. - Disabled it, and so the TypeScript 1.1 rewrite had the same restriction. - Need to do some checking up front to avoid these circularities. - Though it's not that simple; what's the apparent type of `T` here? ``` ts class Foo<T extends Foo<T>> { } ``` though this is solved. - Where it gets interesting. ``` ts function bar<T extends Foo<T>>(x: T): T { } ``` when writing `bar(expr)`, we expect to infer a types of `T` from `expr`. We then need to check the constraint for `T` against `Foo<T>`. - How should this work when inference fails? - Couldn't we just infer `any` when we can't resolve things? - Anders tried that, but then you end up inferring `any` for almost every type parameter of this form which is undesirable - We could resolve a failed inference for `T extends Foo<T>` with `Foo<{}>` - But that's not compatible. - Kind of weird to allow that. - But inference is never supposed to "fail", so what else can we do? - Inferring from union types ``` ts function extract<T>(x: T | string): T { /*...*/ } var x: string | string[]; var y = extract(x); ``` - We decided to adopt the policy that we remove `string` from the union type and just infer `string[]` for `T`. - This is because the two types are identical. - What if we had a recursive call? ``` ts function extract<T>(x: T | string): T { var z = extract(x); } ``` ``` * Right now we refer the type of `z` to be `{}`. * Changes to union type inference will make us infer the type of `z` to be `T`. ``` - Regression caused by implicit type parameters ``` ts class C { } var x: C; ``` is implicitly of like writing ``` ts class C<this> { } var x: C<C>; ``` - Internally, `C` has type flags indicating that it is both a class and a reference (`TypeFlags.Class` and `TypeFlags.Reference`). - Internally, this is just represented by having 1 extra type argument than the number of type parameters. - Caused a regression. - Anders fixed it. - Did we add it to the real world code suite? - No. - We should. # Enum compatibility - continued (#1748) - Dicsussed it a bit last time. - Discussion from last time was inconclusive. - We have a request to make enums structurally compatible. - Why is this a problem? - Two packages that use the same enum from a `.d.ts` that they depend on. - TypeScript has this issue when using its own API as well as TSLint's. - Alternative is to consider some form of "tagging". - e.g. use some sort of GUID to indicate enums are compatible. - Weird - a very "opt-in" behavior. - This is often the desired default behavior. - If this needs to become common practice, it makes enums awkward and daunting. - Easy "gotcha". - What makes two enums "compatible"? - Same name? - That's odd. - We don't do that anywhere else. - Same property names? - Should those matter? - But what about when v2 of a library adds enum members to v1? - But it seems like those libraries aren't compatible anyway, so the two enums are incompatible. - What about when you want to supply a "public" version of your enum? - What about just the values? - Surprise! People hate that. - Being const enums with equivalent values? - But that leaves enums out in the rain. - Const enums can never have "floating dependency versions". - e.g. when writing a library for TypeScript that depends on TypeScript, you need to lock down on the version you compiled against. - Regular enums are better for this because you never have to think about this as long as the members still exist due to late binding. - If we used property names, we'd need to restrict the assignability of the individual members themselves. - What if each enum member had an implicit "parent"? - That way when comparing `Direction.Right` to `Moral.Right`, we need to check if `Direction` and `Moral` are structurally compatible as well. - What about directionality? - Is the one with fewer members assignable to the enum with more members? - What is the motivating example for directionality? - When depending on a library that depends on us where we've hidden part of our own API. - What if we had "enum merging" to split out public and private portions? - Team seems frightened by this, I will never bring this up again. - Order? - No, ordering is a red herring. Values are really what we're interested in. - Let's just get an implementation going so we can make decisions on that. - More or less, seems like we're interested in enums needing the same members with identical name/value pairs. - Lingering question: do we require the enum declaration name to be identical? - You don't want `Volume` and `Weight` to be compatible. - Weird that we recognize the value of nominal checking, but only want to apply it to enums.
Allow type parameter constraints to reference other type parameters (#2304)
Pre 1.0, used to be that we could allow you to write something like
class Foo<T extends X, U extends T>There?though this is solved.
when writing
bar(expr), we expect to infer a types ofTfromexpr.We then need to check the constraint for
TagainstFoo<T>.anywhen we can't resolve things?anyfor almost every type parameter of this form which is undesirableT extends Foo<T>withFoo<{}>Inferring from union types
stringfrom the union type and just inferstring[]forT.Regression caused by implicit type parameters
is implicitly of like writing
Chas type flags indicating that it is both a class and a reference (TypeFlags.ClassandTypeFlags.Reference).Enum compatibility - continued (#1748)
.d.tsthat they depend on.Direction.RighttoMoral.Right, we need to check ifDirectionandMoralare structurally compatible as well.VolumeandWeightto be compatible.