Breaking change for default parameters
Akxe opened this issue · 7 comments
TypeScript Version: 3.3.1
Search Terms:
Type parameter defaults can only reference previously declared type parameters.
Code
interface INamed {
name: string;
}
class Person<T extends INamed = T> {
// ...
}Intended use
I wanted to use T as shorthand for INamed, because it may get very long and unless you make a separate type variable, there wasn't an easy and clean way to do it.
Previous behavior:
no error
Current behavior:
Error: Type parameter defaults can only reference previously declared type parameters.
Playground Link:
Demo
Related Issues:
Not really found anything...
Your code doesn't make much sense.
If you want a shorter name just do something like this:
interface INamed {
name: string;
}
type IN = INamed
But that means that every time you have a generic with default, you may need to define new type variable for it.
To me this was more readable, as to me it was intuitive and clearly told that default is what is written next to it.
I mean, the original behaviour might be told to be bug, and such declared fixed, but to me this would be shame.
I have quite an experience in TS and your code doesn't make sense to me at all. It's the same if you write something like this:
function foo(param: SomeType = param) ...I also can't say if it worked before, but if it did, it was a bug and it is good that it was fixed.
I suppose I will miss this "feature", but I think this issue can be closed.
FYI, this change breaks a different use case for dependent type parameters, which is to provide bounded contravariance for method arguments.
interface Updater<V extends U = Value & U, U = AnyValue> {
getValue(): V;
setValue(value: U): V;
}We use this pattern everywhere. It's quite convenient because it allows us to abstract over the fact that some methods may take a supertype of their return type as an argument. We use Updater<Color, AnyColor>, Updater<Angle, AnyAngle>, etc., when we want to support looser method argument types, but need to ensure that the return type conforms to the argument type (we use this pattern for more than just updaters, and need to be able to recursively feed the return value back into the method).
Where the default type parameters come into play is when V = U. Allowing out-of-order bounds in default type parameters allows use to use the shorthand Updater<string>, and Updater<number>, when the upper and lower type bounds are equivalent. And in the degenerate case, we can simply use Updater, with no type arguments, to get a default strong union type, and weak structural type.
Is there a fundamental language problem inherent to supporting out-of-order type parameter defaults? We have 80,000 lines of TypeScript code that uses this pattern pervasively... Our API would suffer significant ergonomic degradation without this ability.
Please consider reverting this change. Thank you.
Update. It appears most of the places where we rely on default type parameters are in generic methods, not generic types. And for generic methods, overloads seem to satisfactorily solve the ergonomics problem. Nothing to see here. Move along. 👌
We were also using this to get around the issue with #6230:
interface A<a> {
brand: 'a';
nested: a;
}
interface B<a> {
brand: 'b';
nested: a;
}
type C<a> = A<a> | B<a>;
// Doesn't work in any version, see #6230:
// type D = C<D> | string; // <-- i wish
// Works in TS <= 3.2
type D<T extends D<T> = D<T>> = C<T> | string;