Skip to content

Commit a133cec

Browse files
author
Andy
authored
Fix bug: Interface type parameter merged with property is not unused (#21966)
1 parent 1b6aa13 commit a133cec

7 files changed

+115
-29
lines changed

src/compiler/checker.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,7 +1159,7 @@ namespace ts {
11591159
const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location
11601160
let result: Symbol;
11611161
let lastLocation: Node;
1162-
let lastNonBlockLocation: Node;
1162+
let lastSelfReferenceLocation: Node;
11631163
let propertyWithInvalidInitializer: Node;
11641164
const errorLocation = location;
11651165
let grandparent: Node;
@@ -1383,17 +1383,17 @@ namespace ts {
13831383
}
13841384
break;
13851385
}
1386-
if (isNonBlockLocation(location)) {
1387-
lastNonBlockLocation = location;
1386+
if (isSelfReferenceLocation(location)) {
1387+
lastSelfReferenceLocation = location;
13881388
}
13891389
lastLocation = location;
13901390
location = location.parent;
13911391
}
13921392

13931393
// We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`.
1394-
// If `result === lastNonBlockLocation.symbol`, that means that we are somewhere inside `lastNonBlockLocation` looking up a name, and resolving to `lastLocation` itself.
1394+
// If `result === lastSelfReferenceLocation.symbol`, that means that we are somewhere inside `lastSelfReferenceLocation` looking up a name, and resolving to `lastLocation` itself.
13951395
// That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used.
1396-
if (isUse && result && nameNotFoundMessage && noUnusedIdentifiers && result !== lastNonBlockLocation.symbol) {
1396+
if (isUse && result && nameNotFoundMessage && noUnusedIdentifiers && (!lastSelfReferenceLocation || result !== lastSelfReferenceLocation.symbol)) {
13971397
result.isReferenced = true;
13981398
}
13991399

@@ -1476,17 +1476,17 @@ namespace ts {
14761476
return result;
14771477
}
14781478

1479-
function isNonBlockLocation({ kind }: Node): boolean {
1480-
switch (kind) {
1481-
case SyntaxKind.Block:
1482-
case SyntaxKind.ModuleBlock:
1483-
case SyntaxKind.SwitchStatement:
1484-
case SyntaxKind.CaseBlock:
1485-
case SyntaxKind.CaseClause:
1486-
case SyntaxKind.DefaultClause:
1487-
return false;
1488-
default:
1479+
function isSelfReferenceLocation(node: Node): boolean {
1480+
switch (node.kind) {
1481+
case SyntaxKind.FunctionDeclaration:
1482+
case SyntaxKind.ClassDeclaration:
1483+
case SyntaxKind.InterfaceDeclaration:
1484+
case SyntaxKind.EnumDeclaration:
1485+
case SyntaxKind.TypeAliasDeclaration:
1486+
case SyntaxKind.ModuleDeclaration: // For `namespace N { N; }`
14891487
return true;
1488+
default:
1489+
return false;
14901490
}
14911491
}
14921492

tests/baselines/reference/noUnusedLocals_selfReference.errors.txt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ tests/cases/compiler/noUnusedLocals_selfReference.ts(3,10): error TS6133: 'f' is
22
tests/cases/compiler/noUnusedLocals_selfReference.ts(5,14): error TS6133: 'g' is declared but its value is never read.
33
tests/cases/compiler/noUnusedLocals_selfReference.ts(9,7): error TS6133: 'C' is declared but its value is never read.
44
tests/cases/compiler/noUnusedLocals_selfReference.ts(12,6): error TS6133: 'E' is declared but its value is never read.
5-
tests/cases/compiler/noUnusedLocals_selfReference.ts(14,19): error TS6133: 'm' is declared but its value is never read.
5+
tests/cases/compiler/noUnusedLocals_selfReference.ts(13,11): error TS6133: 'I' is declared but its value is never read.
6+
tests/cases/compiler/noUnusedLocals_selfReference.ts(14,6): error TS6133: 'T' is declared but its value is never read.
7+
tests/cases/compiler/noUnusedLocals_selfReference.ts(15,11): error TS6133: 'N' is declared but its value is never read.
8+
tests/cases/compiler/noUnusedLocals_selfReference.ts(22,19): error TS6133: 'm' is declared but its value is never read.
69

710

8-
==== tests/cases/compiler/noUnusedLocals_selfReference.ts (5 errors) ====
11+
==== tests/cases/compiler/noUnusedLocals_selfReference.ts (8 errors) ====
912
export {}; // Make this a module scope, so these are local variables.
1013

1114
function f() {
@@ -26,6 +29,20 @@ tests/cases/compiler/noUnusedLocals_selfReference.ts(14,19): error TS6133: 'm' i
2629
enum E { A = 0, B = E.A }
2730
~
2831
!!! error TS6133: 'E' is declared but its value is never read.
32+
interface I { x: I };
33+
~
34+
!!! error TS6133: 'I' is declared but its value is never read.
35+
type T = { x: T };
36+
~
37+
!!! error TS6133: 'T' is declared but its value is never read.
38+
namespace N { N; }
39+
~
40+
!!! error TS6133: 'N' is declared but its value is never read.
41+
42+
// Avoid a false positive.
43+
// Previously `T` was considered unused due to merging with the property,
44+
// back when all non-blocks were checked for recursion.
45+
export interface A<T> { T: T }
2946

3047
class P { private m() { this.m; } }
3148
~

tests/baselines/reference/noUnusedLocals_selfReference.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ class C {
1111
m() { C; }
1212
}
1313
enum E { A = 0, B = E.A }
14+
interface I { x: I };
15+
type T = { x: T };
16+
namespace N { N; }
17+
18+
// Avoid a false positive.
19+
// Previously `T` was considered unused due to merging with the property,
20+
// back when all non-blocks were checked for recursion.
21+
export interface A<T> { T: T }
1422

1523
class P { private m() { this.m; } }
1624
P;
@@ -40,6 +48,11 @@ var E;
4048
E[E["A"] = 0] = "A";
4149
E[E["B"] = 0] = "B";
4250
})(E || (E = {}));
51+
;
52+
var N;
53+
(function (N) {
54+
N;
55+
})(N || (N = {}));
4356
var P = /** @class */ (function () {
4457
function P() {
4558
}

tests/baselines/reference/noUnusedLocals_selfReference.symbols

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,46 @@ enum E { A = 0, B = E.A }
2929
>E : Symbol(E, Decl(noUnusedLocals_selfReference.ts, 10, 1))
3030
>A : Symbol(E.A, Decl(noUnusedLocals_selfReference.ts, 11, 8))
3131

32+
interface I { x: I };
33+
>I : Symbol(I, Decl(noUnusedLocals_selfReference.ts, 11, 25))
34+
>x : Symbol(I.x, Decl(noUnusedLocals_selfReference.ts, 12, 13))
35+
>I : Symbol(I, Decl(noUnusedLocals_selfReference.ts, 11, 25))
36+
37+
type T = { x: T };
38+
>T : Symbol(T, Decl(noUnusedLocals_selfReference.ts, 12, 21))
39+
>x : Symbol(x, Decl(noUnusedLocals_selfReference.ts, 13, 10))
40+
>T : Symbol(T, Decl(noUnusedLocals_selfReference.ts, 12, 21))
41+
42+
namespace N { N; }
43+
>N : Symbol(N, Decl(noUnusedLocals_selfReference.ts, 13, 18))
44+
>N : Symbol(N, Decl(noUnusedLocals_selfReference.ts, 13, 18))
45+
46+
// Avoid a false positive.
47+
// Previously `T` was considered unused due to merging with the property,
48+
// back when all non-blocks were checked for recursion.
49+
export interface A<T> { T: T }
50+
>A : Symbol(A, Decl(noUnusedLocals_selfReference.ts, 14, 18))
51+
>T : Symbol(T, Decl(noUnusedLocals_selfReference.ts, 19, 19), Decl(noUnusedLocals_selfReference.ts, 19, 23))
52+
>T : Symbol(T, Decl(noUnusedLocals_selfReference.ts, 19, 19), Decl(noUnusedLocals_selfReference.ts, 19, 23))
53+
>T : Symbol(T, Decl(noUnusedLocals_selfReference.ts, 19, 19), Decl(noUnusedLocals_selfReference.ts, 19, 23))
54+
3255
class P { private m() { this.m; } }
33-
>P : Symbol(P, Decl(noUnusedLocals_selfReference.ts, 11, 25))
34-
>m : Symbol(P.m, Decl(noUnusedLocals_selfReference.ts, 13, 9))
35-
>this.m : Symbol(P.m, Decl(noUnusedLocals_selfReference.ts, 13, 9))
36-
>this : Symbol(P, Decl(noUnusedLocals_selfReference.ts, 11, 25))
37-
>m : Symbol(P.m, Decl(noUnusedLocals_selfReference.ts, 13, 9))
56+
>P : Symbol(P, Decl(noUnusedLocals_selfReference.ts, 19, 30))
57+
>m : Symbol(P.m, Decl(noUnusedLocals_selfReference.ts, 21, 9))
58+
>this.m : Symbol(P.m, Decl(noUnusedLocals_selfReference.ts, 21, 9))
59+
>this : Symbol(P, Decl(noUnusedLocals_selfReference.ts, 19, 30))
60+
>m : Symbol(P.m, Decl(noUnusedLocals_selfReference.ts, 21, 9))
3861

3962
P;
40-
>P : Symbol(P, Decl(noUnusedLocals_selfReference.ts, 11, 25))
63+
>P : Symbol(P, Decl(noUnusedLocals_selfReference.ts, 19, 30))
4164

4265
// Does not detect mutual recursion.
4366
function g() { D; }
44-
>g : Symbol(g, Decl(noUnusedLocals_selfReference.ts, 14, 2))
45-
>D : Symbol(D, Decl(noUnusedLocals_selfReference.ts, 17, 19))
67+
>g : Symbol(g, Decl(noUnusedLocals_selfReference.ts, 22, 2))
68+
>D : Symbol(D, Decl(noUnusedLocals_selfReference.ts, 25, 19))
4669

4770
class D { m() { g; } }
48-
>D : Symbol(D, Decl(noUnusedLocals_selfReference.ts, 17, 19))
49-
>m : Symbol(D.m, Decl(noUnusedLocals_selfReference.ts, 18, 9))
50-
>g : Symbol(g, Decl(noUnusedLocals_selfReference.ts, 14, 2))
71+
>D : Symbol(D, Decl(noUnusedLocals_selfReference.ts, 25, 19))
72+
>m : Symbol(D.m, Decl(noUnusedLocals_selfReference.ts, 26, 9))
73+
>g : Symbol(g, Decl(noUnusedLocals_selfReference.ts, 22, 2))
5174

tests/baselines/reference/noUnusedLocals_selfReference.types

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,29 @@ enum E { A = 0, B = E.A }
3030
>E : typeof E
3131
>A : E
3232

33+
interface I { x: I };
34+
>I : I
35+
>x : I
36+
>I : I
37+
38+
type T = { x: T };
39+
>T : { x: T; }
40+
>x : { x: T; }
41+
>T : { x: T; }
42+
43+
namespace N { N; }
44+
>N : typeof N
45+
>N : typeof N
46+
47+
// Avoid a false positive.
48+
// Previously `T` was considered unused due to merging with the property,
49+
// back when all non-blocks were checked for recursion.
50+
export interface A<T> { T: T }
51+
>A : A<T>
52+
>T : T
53+
>T : T
54+
>T : T
55+
3356
class P { private m() { this.m; } }
3457
>P : P
3558
>m : () => void

tests/cases/compiler/noUnusedLocals_selfReference.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// @noUnusedLocals: true
2+
// @noUnusedParameters: true
23

34
export {}; // Make this a module scope, so these are local variables.
45

@@ -12,6 +13,14 @@ class C {
1213
m() { C; }
1314
}
1415
enum E { A = 0, B = E.A }
16+
interface I { x: I };
17+
type T = { x: T };
18+
namespace N { N; }
19+
20+
// Avoid a false positive.
21+
// Previously `T` was considered unused due to merging with the property,
22+
// back when all non-blocks were checked for recursion.
23+
export interface A<T> { T: T }
1524

1625
class P { private m() { this.m; } }
1726
P;

tests/cases/compiler/nounusedTypeParameterConstraint.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
//@noUnusedLocals:true
1+
// @noUnusedLocals: true
2+
// @noUnusedParameters:true
23

34
//@filename: bar.ts
45
export interface IEventSourcedEntity { }

0 commit comments

Comments
 (0)