-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Type's generic type constraint on member level #1290
Comments
BTW, Happy Thanksgiving Day! |
Are there other examples of where this would be needed? It seems pretty specific. |
For me it looks very useful, and I can imagine lot of situations when it necessary (or at least more expressive then using other patterns to describe). I'll be looking for such use cases in DT definitions and JS libraries. Can't remember anyone at the moment (you caught me). I'll provide some use cases. |
Possible dupe of #209 |
There is one more example in #6529. |
Additional explanation and examples + possible usage taken from the duplicate #7083: It's currently possible to do this: method<A extends Array<number>>(one):number;
method<A extends Array<string>>(two):string; But not possible to do this: interface Example<T extends Array<number>> {
method(one):number;
}
interface Example<T extends Array<string>> {
method(two):string;
} This means we cannot have different typings depending on the generic type constraint of the interface. interface Example<T extends Array<any>> {
method<A extends T & Array<number>>(one):number;
method<A extends T & Array<string>>(two):string;
} I was hoping that TS will infer the type of the T array from the intersect, unfortunately that does not happen (it offers both methods, regardless of whether Example is an instance of Among other uses, this feature would be useful for a variety of database libraries that return internal types that can have their own operations done on them. For example, when I The current workaround is to ask the user to explicitly declare both - the outer and the inner type, e.g.: interface Example<TOuter, TInner> {
map<TOut>((item: TInner) => TOut): TOut;
// ...TOuter used in another method
} However, this Last example: an interface What we'd need is to be able to constrain not only by method's own generics, but by the containing interfaces generics, for method signatures themselves, something like: interface Example<T> {
// here the constraint is not of the method, but of the type
method(one):number where T extends Array<number>;
method(two):string where T extends Array<string>;
} |
This is very useful. Any type that provides operations only if its type parameter is specialized enough can greatly beneficiate from the type safety this would bring. Ex: Flattening any kind of nested container like Streams, Arrays. You don't want to be able to call flatten on Another example: Lodash's That said... Using a functional style instead of an OO style make all these issues go away :) |
Would the OP @Igorbek or anyone else like to collaborate on a proposal for this feature? |
@niieani Absolutely. Send me if you have something already. If not, I'll start to formulate a proposal. |
I don't have anything yet, but we can use parts of your initial post and mine #1290 (comment). If you start working on something, let's use something like Google Docs and link here, so we can collaborate on the document. |
I've created a gist to start with https://gist.github.com/Igorbek/21b2bd503fd291d3281def829e2d5fbd |
Hello, note I have updated this example as per @niieani's comment below. This solution is not perfect, but it does help while there is nothing. This works as of typescript 2.0.0 ; class Test<T> {
// note that if there is no member of type T, the errors are not spotted
val: T
// note : can be called with Test<something extends string> as well
strings(this: Test<string>) {
console.log('strings !')
}
numbers(this: Test<number>) {
console.log('numberss')
}
arrays<T>(this: Test<T[]>) {
console.log('arrays')
}
}
let ts = new Test<string>()
let tn = new Test<number>()
let ta = new Test<string[]>()
// Those work, as intended.
ts.strings()
tn.numbers()
ta.arrays()
// those don't
ts.numbers()
tn.strings()
ts.arrays()
/**
test.ts(30,1): error TS2684: The 'this' context of type 'Test<string>' is not assignable to method's 'this' of type 'Test<number>'.
Type 'string' is not assignable to type 'number'.
test.ts(31,1): error TS2684: The 'this' context of type 'Test<number>' is not assignable to method's 'this' of type 'Test<string>'.
Type 'number' is not assignable to type 'string'.
test.ts(32,1): error TS2684: The 'this' context of type 'Test<string>' is not assignable to method's 'this' of type 'Test<string[]>'.
Type 'string' is not assignable to type 'string[]'.
*/ |
@ceymard that's a pretty cool hack, but it still means you need to explicitly type all the generics in case a method has more than one generic type that we want to use. A more complex example, where this breaks apart: class Test<T> {
// note that if there is no member of type T, the errors are not spotted
val: T
strings<S extends string, X>(this: Test<S>) {
console.log('strings !')
return 1 as X
}
numbers<N extends number, X>(this: Test<N>) {
console.log('numberss')
return 1 as X
}
}
let ts = new Test<string>()
let tn = new Test<number>()
// S and N could be inferred, but I X needs to be explicitly stated here, so I can't do this with good results:
ts.strings()
tn.numbers()
// I'd need to duplicate the typings of S and N, which beats the point of inferrence:
ts.strings<string, number>()
tn.numbers<number, number>() It would be best if we could solve this problem without adding making the methods themselves generic. |
If you prefer, you can do that too ; class Test<T> {
// note that if there is no member of type T, the errors are not spotted
val: T
strings<X>(this: Test<string>) {
console.log('strings !')
return 1 as X
}
numbers<X>(this: Test<number>) {
console.log('numberss')
return 1 as X
}
} This still works IIRC, I don't know why I used the <... extends > as it is not mandatory. Also, if you have a I agree it would be best though, but for now that we have nothing, I think this is a "hack" that may help whoever is looking to do just that in 95% of their use case. Doesn't mean this issue should be closed. |
OK, looks cool! Thanks. Sad you still need the |
I think |
Problem
Sometimes type's members could only be applicable with some restrictions of enclosing type parameter (generic type constraints).
For instance, this is use case of RxJS:
Original Rx.NET Merge method implemented as C# extension method on type
IObservable<IObservable<T>>
. In RxJS it's implemented as an instance method.Current RxJS typescript definition just use unsafe trick:
But it can be still unsafely called for as instance of any
Observable<T>
.Possible solutions
Extension methods
Something like C# extension methods. But don't think it's useful in such cases.
BTW, extensions method could be other cool feature, which change call method (from instance-like to static-like).
Multiple interface definitions with different constraints
Member-level constraints with reference to enclosing type arguments
I'll suggest this option. Moreover the previous solution options is special case of this one (incompatible constraints are merged on member level).
The text was updated successfully, but these errors were encountered: