Description
Before You File a Bug Report Please Confirm You Have Done The Following...
- I have tried restarting my IDE and the issue persists.
- I have updated to the latest version of the packages.
- I have searched for related issues and found none that matched my issue.
- I have read the FAQ and my problem is not listed.
Playground Link
Repro Code
declare function wrap1<TArgs extends number[], TAuxFn extends (...args: TArgs) => void>(
fn: (...args: TArgs) => void,
auxFn: TAuxFn,
): ((...args: TArgs) => void);
declare function wrap2<TArgs extends number[]>(
fn: (...args: TArgs) => void,
auxFn: (...args: TArgs) => void, // <- inline extendee of type parameter from `wrap1`
): ((...args: TArgs) => void);
declare function inner(a?: number): void;
const outer1 = wrap1(inner, a => a);
const outer2 = wrap2(inner, a => a);
outer1();
outer2(); // TS2554: Expected 1 arguments, but got 0.
ESLint Config
module.exports = {
"rules": {
"@typescript-eslint/no-unnecessary-type-parameters": "error"
}
}
tsconfig
Expected Result
It is notionally correct for the @typescript-eslint/no-unnecessary-type-parameters
rule to to report a lint error for the TAuxFn
parameter to wrap1
in the repro, since that type parameter really is used only once.
The trouble is that if you resolve this in the standard way, as shown in wrap2
(inline the "base" type for the type parameter at the single use site), you run into trouble at use sites. outer1
is inferred as (a?: number) => void
, which is desired/correct. But outer2
is inferred as (a: number) => void
- it has lost the ?
optional-ness on its argument.
So, it seems like it'd be good if this rule were clever enough to not report errors in cases where keeping a single-use type parameter around is necessary to avoid an adverse change in how types are inferred.
Actual Result
A lint error is reported for TAuxFn
in wrap1
, necessitating an eslint-disable
directive to suppress it, since there's not really a way to fix it.
Additional Info
This is a reduced reproduction from a personal codebase, in which the implementation of wrap1
is morally like:
function wrap1(fn, auxFn) {
const wrappedFn = (...args) => {
// do some pre-work
const result = fn(...args);
const cacheKey = auxFn(...args);
globalCache[cacheKey] = result;
return result;
};
return wrappedFn;
}
Note that strictFunctionTypes
is required in the repro above. Without strictFunctionTypes
, wrap1
and wrap2
both infer as (a?: number) => void
.