Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -6694,8 +6694,9 @@ func (c *Checker) checkUnusedIdentifiers(potentiallyUnusedIdentifiers []*ast.Nod
// Only report unused parameters on the implementation, not overloads.
if node.Body() != nil {
c.checkUnusedLocalsAndParameters(node)
// Only report unused type parameters on the implementation, not overloads.
c.checkUnusedTypeParameters(node)
}
Copy link
Member

@jakebailey jakebailey Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original code for checkUnusedIdentifiers in checker.ts is:

case SyntaxKind.Constructor:
case SyntaxKind.FunctionExpression:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ArrowFunction:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
    // Only report unused parameters on the implementation, not overloads.
    if (node.body) {
        checkUnusedLocalsAndParameters(node, addDiagnostic);
    }
    checkUnusedTypeParameters(node, addDiagnostic);
    break;

Which very much implies that we were checking unused type parameters on these.

I think the difference is actually in checkUnusedTypeParameters, which did this:

        // Only report errors on the last declaration for the type parameter container;
        // this ensures that all uses have been accounted for.
        const declarations = getSymbolOfDeclaration(node).declarations;
        if (!declarations || last(declarations) !== node) return;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, what I'm saying is, I think that this PR is actually correct as it skips the unused check exactly like the other code.

c.checkUnusedTypeParameters(node)
case ast.KindMethodSignature, ast.KindCallSignature, ast.KindConstructSignature, ast.KindFunctionType, ast.KindConstructorType,
ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration, ast.KindInterfaceDeclaration:
c.checkUnusedTypeParameters(node)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
overloadUnusedTypeParameters.ts(3,30): error TS6196: 'T' is declared but never used.
overloadUnusedTypeParameters.ts(13,31): error TS6196: 'T' is declared but never used.


==== overloadUnusedTypeParameters.ts (2 errors) ====
export function tryParseJson<T>(text: string): unknown;
export function tryParseJson<T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined;
export function tryParseJson<T>() {
~
!!! error TS6196: 'T' is declared but never used.
throw new Error("noop")
}

export function tryParseJson2<T>(text: string): unknown;
export function tryParseJson2<T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined;
export function tryParseJson2() {
throw new Error("noop")
}

export function tryParseJson3<T>(_text: string): unknown {
~
!!! error TS6196: 'T' is declared but never used.
throw new Error("noop")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//// [tests/cases/compiler/overloadUnusedTypeParameters.ts] ////

//// [overloadUnusedTypeParameters.ts]
export function tryParseJson<T>(text: string): unknown;
export function tryParseJson<T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined;
export function tryParseJson<T>() {
throw new Error("noop")
}

export function tryParseJson2<T>(text: string): unknown;
export function tryParseJson2<T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined;
export function tryParseJson2() {
throw new Error("noop")
}

export function tryParseJson3<T>(_text: string): unknown {
throw new Error("noop")
}

//// [overloadUnusedTypeParameters.js]
export function tryParseJson() {
throw new Error("noop");
}
export function tryParseJson2() {
throw new Error("noop");
}
export function tryParseJson3(_text) {
throw new Error("noop");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//// [tests/cases/compiler/overloadUnusedTypeParameters.ts] ////

=== overloadUnusedTypeParameters.ts ===
export function tryParseJson<T>(text: string): unknown;
>tryParseJson : Symbol(tryParseJson, Decl(overloadUnusedTypeParameters.ts, 0, 0), Decl(overloadUnusedTypeParameters.ts, 0, 55), Decl(overloadUnusedTypeParameters.ts, 1, 106))
>T : Symbol(T, Decl(overloadUnusedTypeParameters.ts, 0, 29))
>text : Symbol(text, Decl(overloadUnusedTypeParameters.ts, 0, 32))

export function tryParseJson<T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined;
>tryParseJson : Symbol(tryParseJson, Decl(overloadUnusedTypeParameters.ts, 0, 0), Decl(overloadUnusedTypeParameters.ts, 0, 55), Decl(overloadUnusedTypeParameters.ts, 1, 106))
>T : Symbol(T, Decl(overloadUnusedTypeParameters.ts, 1, 29))
>text : Symbol(text, Decl(overloadUnusedTypeParameters.ts, 1, 32))
>predicate : Symbol(predicate, Decl(overloadUnusedTypeParameters.ts, 1, 45))
>parsed : Symbol(parsed, Decl(overloadUnusedTypeParameters.ts, 1, 58))
>parsed : Symbol(parsed, Decl(overloadUnusedTypeParameters.ts, 1, 58))
>T : Symbol(T, Decl(overloadUnusedTypeParameters.ts, 1, 29))
>T : Symbol(T, Decl(overloadUnusedTypeParameters.ts, 1, 29))

export function tryParseJson<T>() {
>tryParseJson : Symbol(tryParseJson, Decl(overloadUnusedTypeParameters.ts, 0, 0), Decl(overloadUnusedTypeParameters.ts, 0, 55), Decl(overloadUnusedTypeParameters.ts, 1, 106))
>T : Symbol(T, Decl(overloadUnusedTypeParameters.ts, 2, 29))

throw new Error("noop")
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --))
}

export function tryParseJson2<T>(text: string): unknown;
>tryParseJson2 : Symbol(tryParseJson2, Decl(overloadUnusedTypeParameters.ts, 4, 1), Decl(overloadUnusedTypeParameters.ts, 6, 56), Decl(overloadUnusedTypeParameters.ts, 7, 107))
>T : Symbol(T, Decl(overloadUnusedTypeParameters.ts, 6, 30))
>text : Symbol(text, Decl(overloadUnusedTypeParameters.ts, 6, 33))

export function tryParseJson2<T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined;
>tryParseJson2 : Symbol(tryParseJson2, Decl(overloadUnusedTypeParameters.ts, 4, 1), Decl(overloadUnusedTypeParameters.ts, 6, 56), Decl(overloadUnusedTypeParameters.ts, 7, 107))
>T : Symbol(T, Decl(overloadUnusedTypeParameters.ts, 7, 30))
>text : Symbol(text, Decl(overloadUnusedTypeParameters.ts, 7, 33))
>predicate : Symbol(predicate, Decl(overloadUnusedTypeParameters.ts, 7, 46))
>parsed : Symbol(parsed, Decl(overloadUnusedTypeParameters.ts, 7, 59))
>parsed : Symbol(parsed, Decl(overloadUnusedTypeParameters.ts, 7, 59))
>T : Symbol(T, Decl(overloadUnusedTypeParameters.ts, 7, 30))
>T : Symbol(T, Decl(overloadUnusedTypeParameters.ts, 7, 30))

export function tryParseJson2() {
>tryParseJson2 : Symbol(tryParseJson2, Decl(overloadUnusedTypeParameters.ts, 4, 1), Decl(overloadUnusedTypeParameters.ts, 6, 56), Decl(overloadUnusedTypeParameters.ts, 7, 107))

throw new Error("noop")
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --))
}

export function tryParseJson3<T>(_text: string): unknown {
>tryParseJson3 : Symbol(tryParseJson3, Decl(overloadUnusedTypeParameters.ts, 10, 1))
>T : Symbol(T, Decl(overloadUnusedTypeParameters.ts, 12, 30))
>_text : Symbol(_text, Decl(overloadUnusedTypeParameters.ts, 12, 33))

throw new Error("noop")
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//// [tests/cases/compiler/overloadUnusedTypeParameters.ts] ////

=== overloadUnusedTypeParameters.ts ===
export function tryParseJson<T>(text: string): unknown;
>tryParseJson : { <T>(text: string): unknown; <T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined; }
>text : string

export function tryParseJson<T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined;
>tryParseJson : { <T>(text: string): unknown; <T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined; }
>text : string
>predicate : (parsed: unknown) => parsed is T
>parsed : unknown

export function tryParseJson<T>() {
>tryParseJson : { <T>(text: string): unknown; <T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined; }

throw new Error("noop")
>new Error("noop") : Error
>Error : ErrorConstructor
>"noop" : "noop"
}

export function tryParseJson2<T>(text: string): unknown;
>tryParseJson2 : { <T>(text: string): unknown; <T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined; }
>text : string

export function tryParseJson2<T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined;
>tryParseJson2 : { <T>(text: string): unknown; <T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined; }
>text : string
>predicate : (parsed: unknown) => parsed is T
>parsed : unknown

export function tryParseJson2() {
>tryParseJson2 : { <T>(text: string): unknown; <T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined; }

throw new Error("noop")
>new Error("noop") : Error
>Error : ErrorConstructor
>"noop" : "noop"
}

export function tryParseJson3<T>(_text: string): unknown {
>tryParseJson3 : <T>(_text: string) => unknown
>_text : string

throw new Error("noop")
>new Error("noop") : Error
>Error : ErrorConstructor
>"noop" : "noop"
}
20 changes: 20 additions & 0 deletions testdata/tests/cases/compiler/overloadUnusedTypeParameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// @strict: true
// @noUnusedLocals: true
// @noUnusedParameters: true
// @target: esnext

export function tryParseJson<T>(text: string): unknown;
export function tryParseJson<T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined;
export function tryParseJson<T>() {
throw new Error("noop")
}

export function tryParseJson2<T>(text: string): unknown;
export function tryParseJson2<T>(text: string, predicate: (parsed: unknown) => parsed is T): T | undefined;
export function tryParseJson2() {
throw new Error("noop")
}

export function tryParseJson3<T>(_text: string): unknown {
throw new Error("noop")
}
Loading