Skip to content

Commit 3a8c68f

Browse files
committed
Add conditions to check for class extension or implementation of primitive types like 'number'
1 parent fdb2531 commit 3a8c68f

12 files changed

+320
-15
lines changed

src/compiler/checker.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3755,6 +3755,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
37553755
if (isExtendedByInterface(errorLocation)) {
37563756
error(errorLocation, Diagnostics.An_interface_cannot_extend_a_primitive_type_like_0_an_interface_can_only_extend_named_types_and_classes, unescapeLeadingUnderscores(name));
37573757
}
3758+
else if (isExtendedByClass(errorLocation)) {
3759+
error(errorLocation, Diagnostics.A_class_cannot_extend_a_primitive_type_like_0_a_class_can_only_extend_named_types_and_classes, unescapeLeadingUnderscores(name));
3760+
}
3761+
else if (isImplementedByClass(errorLocation)) {
3762+
error(errorLocation, Diagnostics.A_class_cannot_implement_a_primitive_type_like_0_a_class_can_only_implement_interfaces, unescapeLeadingUnderscores(name));
3763+
}
37583764
else {
37593765
error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, unescapeLeadingUnderscores(name));
37603766
}
@@ -3790,6 +3796,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
37903796
return false;
37913797
}
37923798

3799+
function isExtendedByClass(node: Node): boolean {
3800+
const grandparent = node.parent.parent;
3801+
const parentOfGrandparent = grandparent.parent;
3802+
if(grandparent && parentOfGrandparent){
3803+
const isExtending = isHeritageClause(grandparent) && grandparent.token === SyntaxKind.ExtendsKeyword;
3804+
const isClass = isClassDeclaration(parentOfGrandparent);
3805+
return isExtending && isClass;
3806+
}
3807+
return false;
3808+
}
3809+
3810+
function isImplementedByClass(node: Node): boolean {
3811+
const grandparent = node.parent.parent;
3812+
const parentOfGrandparent = grandparent.parent;
3813+
if(grandparent && parentOfGrandparent){
3814+
const isImplementing = isHeritageClause(grandparent) && grandparent.token === SyntaxKind.ImplementsKeyword;
3815+
const isClass = isClassDeclaration(parentOfGrandparent);
3816+
return isImplementing && isClass;
3817+
}
3818+
return false;
3819+
}
3820+
37933821
function maybeMappedType(node: Node, symbol: Symbol) {
37943822
const container = findAncestor(node.parent, n => isComputedPropertyName(n) || isPropertySignature(n) ? false : isTypeLiteralNode(n) || "quit") as TypeLiteralNode | undefined;
37953823
if (container && container.members.length === 1) {

src/compiler/diagnosticMessages.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3607,7 +3607,7 @@
36073607
"category": "Error",
36083608
"code": 2839
36093609
},
3610-
"An interface cannot extend a primitive type like '{0}'; an interface can only extend named types and classes": {
3610+
"An interface cannot extend a primitive type like '{0}'; an interface can only extend named types and classes.": {
36113611
"category": "Error",
36123612
"code": 2840
36133613
},
@@ -3663,6 +3663,14 @@
36633663
"category": "Error",
36643664
"code": 2854
36653665
},
3666+
"A class cannot extend a primitive type like '{0}'; a class can only extend named types and classes.": {
3667+
"category": "Error",
3668+
"code": 2855
3669+
},
3670+
"A class cannot implement a primitive type like '{0}'; a class can only implement interfaces.": {
3671+
"category": "Error",
3672+
"code": 2856
3673+
},
36663674

36673675
"Import declaration '{0}' is using private name '{1}'.": {
36683676
"category": "Error",

tests/baselines/reference/classExtendingPrimitive.errors.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
classExtendingPrimitive.ts(3,17): error TS2693: 'number' only refers to a type, but is being used as a value here.
2-
classExtendingPrimitive.ts(4,18): error TS2693: 'string' only refers to a type, but is being used as a value here.
3-
classExtendingPrimitive.ts(5,18): error TS2693: 'boolean' only refers to a type, but is being used as a value here.
1+
classExtendingPrimitive.ts(3,17): error TS2855: A class cannot extend a primitive type like 'number'; a class can only extend named types and classes.
2+
classExtendingPrimitive.ts(4,18): error TS2855: A class cannot extend a primitive type like 'string'; a class can only extend named types and classes.
3+
classExtendingPrimitive.ts(5,18): error TS2855: A class cannot extend a primitive type like 'boolean'; a class can only extend named types and classes.
44
classExtendingPrimitive.ts(6,18): error TS2304: Cannot find name 'Void'.
55
classExtendingPrimitive.ts(7,19): error TS1109: Expression expected.
66
classExtendingPrimitive.ts(8,18): error TS2304: Cannot find name 'Null'.
@@ -14,13 +14,13 @@ classExtendingPrimitive.ts(14,18): error TS2507: Type 'typeof E' is not a constr
1414

1515
class C extends number { }
1616
~~~~~~
17-
!!! error TS2693: 'number' only refers to a type, but is being used as a value here.
17+
!!! error TS2855: A class cannot extend a primitive type like 'number'; a class can only extend named types and classes.
1818
class C2 extends string { }
1919
~~~~~~
20-
!!! error TS2693: 'string' only refers to a type, but is being used as a value here.
20+
!!! error TS2855: A class cannot extend a primitive type like 'string'; a class can only extend named types and classes.
2121
class C3 extends boolean { }
2222
~~~~~~~
23-
!!! error TS2693: 'boolean' only refers to a type, but is being used as a value here.
23+
!!! error TS2855: A class cannot extend a primitive type like 'boolean'; a class can only extend named types and classes.
2424
class C4 extends Void { }
2525
~~~~
2626
!!! error TS2304: Cannot find name 'Void'.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
classImplementsPrimitive.ts(3,20): error TS2856: A class cannot implement a primitive type like 'number'; a class can only implement interfaces.
2+
classImplementsPrimitive.ts(4,21): error TS2856: A class cannot implement a primitive type like 'string'; a class can only implement interfaces.
3+
classImplementsPrimitive.ts(5,21): error TS2856: A class cannot implement a primitive type like 'boolean'; a class can only implement interfaces.
4+
classImplementsPrimitive.ts(6,21): error TS2304: Cannot find name 'Void'.
5+
classImplementsPrimitive.ts(7,7): error TS2300: Duplicate identifier 'C4a'.
6+
classImplementsPrimitive.ts(7,22): error TS1109: Expression expected.
7+
classImplementsPrimitive.ts(8,21): error TS2304: Cannot find name 'Null'.
8+
classImplementsPrimitive.ts(9,7): error TS2300: Duplicate identifier 'C5a'.
9+
classImplementsPrimitive.ts(9,22): error TS2500: A class can only implement an identifier/qualified-name with optional type arguments.
10+
classImplementsPrimitive.ts(10,21): error TS2749: 'undefined' refers to a value, but is being used as a type here. Did you mean 'typeof undefined'?
11+
classImplementsPrimitive.ts(11,21): error TS2304: Cannot find name 'Undefined'.
12+
classImplementsPrimitive.ts(14,21): error TS2422: A class can only implement an object type or intersection of object types with statically known members.
13+
classImplementsPrimitive.ts(16,7): error TS2300: Duplicate identifier 'C4a'.
14+
classImplementsPrimitive.ts(16,22): error TS1109: Expression expected.
15+
classImplementsPrimitive.ts(17,7): error TS2300: Duplicate identifier 'C5a'.
16+
classImplementsPrimitive.ts(17,22): error TS2500: A class can only implement an identifier/qualified-name with optional type arguments.
17+
18+
19+
==== classImplementsPrimitive.ts (16 errors) ====
20+
// classes cannot implement primitives
21+
22+
class C implements number { }
23+
~~~~~~
24+
!!! error TS2856: A class cannot implement a primitive type like 'number'; a class can only implement interfaces.
25+
class C2 implements string { }
26+
~~~~~~
27+
!!! error TS2856: A class cannot implement a primitive type like 'string'; a class can only implement interfaces.
28+
class C3 implements boolean { }
29+
~~~~~~~
30+
!!! error TS2856: A class cannot implement a primitive type like 'boolean'; a class can only implement interfaces.
31+
class C4 implements Void { }
32+
~~~~
33+
!!! error TS2304: Cannot find name 'Void'.
34+
class C4a implements void {}
35+
~~~
36+
!!! error TS2300: Duplicate identifier 'C4a'.
37+
~~~~
38+
!!! error TS1109: Expression expected.
39+
class C5 implements Null { }
40+
~~~~
41+
!!! error TS2304: Cannot find name 'Null'.
42+
class C5a implements null { }
43+
~~~
44+
!!! error TS2300: Duplicate identifier 'C5a'.
45+
~~~~
46+
!!! error TS2500: A class can only implement an identifier/qualified-name with optional type arguments.
47+
class C6 implements undefined { }
48+
~~~~~~~~~
49+
!!! error TS2749: 'undefined' refers to a value, but is being used as a type here. Did you mean 'typeof undefined'?
50+
class C7 implements Undefined { }
51+
~~~~~~~~~
52+
!!! error TS2304: Cannot find name 'Undefined'.
53+
54+
enum E { A }
55+
class C8 implements E { }
56+
~
57+
!!! error TS2422: A class can only implement an object type or intersection of object types with statically known members.
58+
59+
class C4a implements void {}
60+
~~~
61+
!!! error TS2300: Duplicate identifier 'C4a'.
62+
~~~~
63+
!!! error TS1109: Expression expected.
64+
class C5a implements null { }
65+
~~~
66+
!!! error TS2300: Duplicate identifier 'C5a'.
67+
~~~~
68+
!!! error TS2500: A class can only implement an identifier/qualified-name with optional type arguments.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//// [tests/cases/compiler/classImplementsPrimitive.ts] ////
2+
3+
//// [classImplementsPrimitive.ts]
4+
// classes cannot implement primitives
5+
6+
class C implements number { }
7+
class C2 implements string { }
8+
class C3 implements boolean { }
9+
class C4 implements Void { }
10+
class C4a implements void {}
11+
class C5 implements Null { }
12+
class C5a implements null { }
13+
class C6 implements undefined { }
14+
class C7 implements Undefined { }
15+
16+
enum E { A }
17+
class C8 implements E { }
18+
19+
class C4a implements void {}
20+
class C5a implements null { }
21+
22+
//// [classImplementsPrimitive.js]
23+
// classes cannot implement primitives
24+
var C = /** @class */ (function () {
25+
function C() {
26+
}
27+
return C;
28+
}());
29+
var C2 = /** @class */ (function () {
30+
function C2() {
31+
}
32+
return C2;
33+
}());
34+
var C3 = /** @class */ (function () {
35+
function C3() {
36+
}
37+
return C3;
38+
}());
39+
var C4 = /** @class */ (function () {
40+
function C4() {
41+
}
42+
return C4;
43+
}());
44+
var C4a = /** @class */ (function () {
45+
function C4a() {
46+
}
47+
return C4a;
48+
}());
49+
void {};
50+
var C5 = /** @class */ (function () {
51+
function C5() {
52+
}
53+
return C5;
54+
}());
55+
var C5a = /** @class */ (function () {
56+
function C5a() {
57+
}
58+
return C5a;
59+
}());
60+
var C6 = /** @class */ (function () {
61+
function C6() {
62+
}
63+
return C6;
64+
}());
65+
var C7 = /** @class */ (function () {
66+
function C7() {
67+
}
68+
return C7;
69+
}());
70+
var E;
71+
(function (E) {
72+
E[E["A"] = 0] = "A";
73+
})(E || (E = {}));
74+
var C8 = /** @class */ (function () {
75+
function C8() {
76+
}
77+
return C8;
78+
}());
79+
var C4a = /** @class */ (function () {
80+
function C4a() {
81+
}
82+
return C4a;
83+
}());
84+
void {};
85+
var C5a = /** @class */ (function () {
86+
function C5a() {
87+
}
88+
return C5a;
89+
}());
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//// [tests/cases/compiler/classImplementsPrimitive.ts] ////
2+
3+
=== classImplementsPrimitive.ts ===
4+
// classes cannot implement primitives
5+
6+
class C implements number { }
7+
>C : Symbol(C, Decl(classImplementsPrimitive.ts, 0, 0))
8+
9+
class C2 implements string { }
10+
>C2 : Symbol(C2, Decl(classImplementsPrimitive.ts, 2, 29))
11+
12+
class C3 implements boolean { }
13+
>C3 : Symbol(C3, Decl(classImplementsPrimitive.ts, 3, 30))
14+
15+
class C4 implements Void { }
16+
>C4 : Symbol(C4, Decl(classImplementsPrimitive.ts, 4, 31))
17+
18+
class C4a implements void {}
19+
>C4a : Symbol(C4a, Decl(classImplementsPrimitive.ts, 5, 29))
20+
21+
class C5 implements Null { }
22+
>C5 : Symbol(C5, Decl(classImplementsPrimitive.ts, 6, 28))
23+
24+
class C5a implements null { }
25+
>C5a : Symbol(C5a, Decl(classImplementsPrimitive.ts, 7, 28))
26+
27+
class C6 implements undefined { }
28+
>C6 : Symbol(C6, Decl(classImplementsPrimitive.ts, 8, 29))
29+
30+
class C7 implements Undefined { }
31+
>C7 : Symbol(C7, Decl(classImplementsPrimitive.ts, 9, 33))
32+
33+
enum E { A }
34+
>E : Symbol(E, Decl(classImplementsPrimitive.ts, 10, 33))
35+
>A : Symbol(E.A, Decl(classImplementsPrimitive.ts, 12, 8))
36+
37+
class C8 implements E { }
38+
>C8 : Symbol(C8, Decl(classImplementsPrimitive.ts, 12, 12))
39+
>E : Symbol(E, Decl(classImplementsPrimitive.ts, 10, 33))
40+
41+
class C4a implements void {}
42+
>C4a : Symbol(C4a, Decl(classImplementsPrimitive.ts, 13, 25))
43+
44+
class C5a implements null { }
45+
>C5a : Symbol(C5a, Decl(classImplementsPrimitive.ts, 15, 28))
46+
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//// [tests/cases/compiler/classImplementsPrimitive.ts] ////
2+
3+
=== classImplementsPrimitive.ts ===
4+
// classes cannot implement primitives
5+
6+
class C implements number { }
7+
>C : C
8+
9+
class C2 implements string { }
10+
>C2 : C2
11+
12+
class C3 implements boolean { }
13+
>C3 : C3
14+
15+
class C4 implements Void { }
16+
>C4 : C4
17+
18+
class C4a implements void {}
19+
>C4a : C4a
20+
>void {} : undefined
21+
>{} : {}
22+
23+
class C5 implements Null { }
24+
>C5 : C5
25+
26+
class C5a implements null { }
27+
>C5a : C5a
28+
29+
class C6 implements undefined { }
30+
>C6 : C6
31+
32+
class C7 implements Undefined { }
33+
>C7 : C7
34+
35+
enum E { A }
36+
>E : E
37+
>A : E.A
38+
39+
class C8 implements E { }
40+
>C8 : C8
41+
42+
class C4a implements void {}
43+
>C4a : C4a
44+
>void {} : undefined
45+
>{} : {}
46+
47+
class C5a implements null { }
48+
>C5a : C5a
49+
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
errorLocationForInterfaceExtension.ts(3,21): error TS2840: An interface cannot extend a primitive type like 'string'; an interface can only extend named types and classes
1+
errorLocationForInterfaceExtension.ts(3,21): error TS2840: An interface cannot extend a primitive type like 'string'; an interface can only extend named types and classes.
22

33

44
==== errorLocationForInterfaceExtension.ts (1 errors) ====
55
var n = '';
66

77
interface x extends string { }
88
~~~~~~
9-
!!! error TS2840: An interface cannot extend a primitive type like 'string'; an interface can only extend named types and classes
9+
!!! error TS2840: An interface cannot extend a primitive type like 'string'; an interface can only extend named types and classes.
1010

tests/baselines/reference/interfacedeclWithIndexerErrors.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ interfacedeclWithIndexerErrors.ts(14,5): error TS2411: Property 'p4' of type 'nu
33
interfacedeclWithIndexerErrors.ts(15,5): error TS2411: Property 'p5' of type '(s: number) => string' is not assignable to 'string' index type '() => string'.
44
interfacedeclWithIndexerErrors.ts(19,5): error TS2411: Property 'f3' of type '(a: string) => number' is not assignable to 'string' index type '() => string'.
55
interfacedeclWithIndexerErrors.ts(20,5): error TS2411: Property 'f4' of type '(s: number) => string' is not assignable to 'string' index type '() => string'.
6-
interfacedeclWithIndexerErrors.ts(44,21): error TS2840: An interface cannot extend a primitive type like 'number'; an interface can only extend named types and classes
6+
interfacedeclWithIndexerErrors.ts(44,21): error TS2840: An interface cannot extend a primitive type like 'number'; an interface can only extend named types and classes.
77
interfacedeclWithIndexerErrors.ts(48,18): error TS2693: 'string' only refers to a type, but is being used as a value here.
88

99

@@ -63,7 +63,7 @@ interfacedeclWithIndexerErrors.ts(48,18): error TS2693: 'string' only refers to
6363

6464
interface e extends number {
6565
~~~~~~
66-
!!! error TS2840: An interface cannot extend a primitive type like 'number'; an interface can only extend named types and classes
66+
!!! error TS2840: An interface cannot extend a primitive type like 'number'; an interface can only extend named types and classes.
6767
}
6868

6969
interface f {

tests/baselines/reference/topLevelAwaitErrors.1(module=es2022).errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ topLevelAwaitErrors.1.ts(8,14): error TS1005: '>' expected.
66
topLevelAwaitErrors.1.ts(8,16): error TS2693: 'string' only refers to a type, but is being used as a value here.
77
topLevelAwaitErrors.1.ts(11,17): error TS1109: Expression expected.
88
topLevelAwaitErrors.1.ts(11,22): error TS1109: Expression expected.
9-
topLevelAwaitErrors.1.ts(11,23): error TS2693: 'string' only refers to a type, but is being used as a value here.
9+
topLevelAwaitErrors.1.ts(11,23): error TS2855: A class cannot extend a primitive type like 'string'; a class can only extend named types and classes.
1010
topLevelAwaitErrors.1.ts(11,29): error TS1005: ',' expected.
1111
topLevelAwaitErrors.1.ts(15,8): error TS1109: Expression expected.
1212
topLevelAwaitErrors.1.ts(18,2): error TS1109: Expression expected.
@@ -49,7 +49,7 @@ topLevelAwaitErrors.1.ts(42,20): error TS1109: Expression expected.
4949
~
5050
!!! error TS1109: Expression expected.
5151
~~~~~~
52-
!!! error TS2693: 'string' only refers to a type, but is being used as a value here.
52+
!!! error TS2855: A class cannot extend a primitive type like 'string'; a class can only extend named types and classes.
5353
~
5454
!!! error TS1005: ',' expected.
5555
}

0 commit comments

Comments
 (0)