/**
* @constructor
* @template {string} K
* @template V
*/
function Multimap() {
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
this._map = {};
};
var Ns = {}
/** @type {Multimap<"a" | "b", number>} */
const map = new Multimap();
const n = map._map['hi']
Expected behavior:
n: number
Actual behavior:
n: any in 3.0; n: V in 3.1-dev.
Types resolved from functions are never properly generic, even that function has @template-specified type parameters; they're only special-cased in a few places to produce a specific instantiation of a type. They should use the normal generic type machinery that Typescript does.
Hello.
I'm hope this is the right place for this issue. I tried opening a new bug issue but got discouraged.
My goal is to use JavaScript along with .d.ts declaration files.
So my workflow looks a little like this.
This is what my declaration file looks like.
// test.d.ts
export declare function addToCollection<T>( collection:T[], val:T):T
export as namespace test;
Then I implement it in a JS file like this.
// test.js
/**
* @type { test.addToCollection }
**/
export function addToCollection( collection, val ) {
return collection.push( val )
}
The problem here is that Generics are ignored. If the addToCollection function declaration were not generic, it would work. But a generic declaration would be ignored.
Any suggestions on this, or am I using it wrong?
@michaelolof You are using it wrong. test.d.ts applies for users of test.js, not test.js itself. The types of test.js should come from JSDoc in its source, or from packages that it imports.
For your example, you should change the JSDoc for addToCollection
/**
* @template T
* @param {T[]} collection
* @param {T} val
* @returns {T}
*/
and you should no longer need test.d.ts.
Thank you for replying.
I kind of understand where you're coming from and I can probably tell this pattern is definitely not conventional.
My issue with the JSDoc approach, is that it can get really verbose.
The above example is a very simple use case for generics.
Consider a typed pipe function like this in a typescript file.
export function pipe<T1, R>(
fn1: (arg1: T1) => R,
...fns: Array<(a: R) => R>
): (arg1: T1) => R;
export function pipe<T1, T2, R>(
fn1: (arg1: T1, arg2: T2) => R,
...fns: Array<(a: R) => R>
): (arg1: T1, arg2: T2) => R;
export function pipe<T1, T2, T3, R>(
fn1: (arg1: T1, arg2: T2, arg3: T3) => R,
...fns: Array<(a: R) => R>
): (arg1: T1, arg2: T2, arg3: T3) => R;
export function pipe<T1, T2, T3, T4, R>(
fn1: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => R,
...fns: Array<(a: R) => R>
): (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => R;
export function pipe<R>(
fn1: (...args: any[]) => R,
...fns: Array<(a: R) => R>
): (a: R) => R {
return fns.reduce((prevFn, nextFn) => value => nextFn(prevFn(value)), fn1);
}
I shudder to think of how this would be implemented with JSDoc.
Even the TypeScript approach is still a bit to noisy. I'm in an environment where my team is still warming up to TypeScript and I'm scared seeing something like this could easily put them off.
For me i believe the biggest benefit of this .d.ts approach is hiding away all the noise that comes with complicated typings like this. There's also the advantage of seamlessly sharing interfaces extending interfaces etc. without JSDocing your entire codebase.
Also you have to consider this is an approach that works already in TypeScript. It just breaks when using generics.
I hope you do consider it.
Great job with all the work @typescript.
I shudder to think of how this would be implemented with JSDoc.
/** @typedef {{
* <T1, R>(
* fn1: (arg1: T1) => R,
* ...fns: Array<(a: R) => R>
* ): (arg1: T1) => R;
* <T1, T2, R>(
* fn1: (arg1: T1, arg2: T2) => R,
* ...fns: Array<(a: R) => R>
* ): (arg1: T1, arg2: T2) => R;
* <T1, T2, T3, R>(
* fn1: (arg1: T1, arg2: T2, arg3: T3) => R,
* ...fns: Array<(a: R) => R>
* ): (arg1: T1, arg2: T2, arg3: T3) => R;
* <T1, T2, T3, T4, R>(
* fn1: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => R,
* ...fns: Array<(a: R) => R>
* ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => R;
* }} PipeFunction
*/
/** @type {PipeFunction} */
const pipe = (fn1, ...fns) => {
return fns.reduce((prevFn, nextFn) => value => nextFn(prevFn(value)), fn1);
};
Yes I've tried this. It's verbose but still suffers from the same problem as the .d.ts approach.
fn1 and fns are of type any. It is the same problem I faced using declaration files. For a simple one line implementation like the as above this might not be a problem, but implementing a generic function with about 7+ lines of code and you start to see how this might be a problem.
This is where the internet led me regarding how to call functions with specific generic args using JSDoc.
How would we call, for example, the above PipeFunction with specific generic args instead of inferred ones? Or does JSDoc not support explicit generic args for function calls?
@sandersn If I understand correctly, that's what you're saying we can't currently do, right?
It would be really great to have all the features of TS in JSDoc, because then it means we could use generic JSDoc tooling (not just the too-inflexible TSDoc) without duplicating type information in both source and comments.
@trusktr You are correct, there is no way to call functions with type arguments. We view it as a code smell in TS, since it's basically a more-widely-propagated, but non-obvious, cast. I recommend just using a cast in JS, and later rewriting the function so that its type arguments can be inferred [1].
We do not have general plans to replicate all of TS's features in JS, since we think that most people who want TS' features will eventually switch to TS anyway. The main user we care about for JSDoc is the one who never thinks about TS, or types for that matter.
[1] This may mean getting rid of all type parameters and forcing callers to cast.
Most helpful comment
Thank you for replying.
I kind of understand where you're coming from and I can probably tell this pattern is definitely not conventional.
My issue with the JSDoc approach, is that it can get really verbose.
The above example is a very simple use case for generics.
Consider a typed pipe function like this in a typescript file.
I shudder to think of how this would be implemented with JSDoc.
Even the TypeScript approach is still a bit to noisy. I'm in an environment where my team is still warming up to TypeScript and I'm scared seeing something like this could easily put them off.
For me i believe the biggest benefit of this
.d.tsapproach is hiding away all the noise that comes with complicated typings like this. There's also the advantage of seamlessly sharing interfaces extending interfaces etc. without JSDocing your entire codebase.Also you have to consider this is an approach that works already in TypeScript. It just breaks when using generics.
I hope you do consider it.
Great job with all the work @typescript.