Skip to content

Commit 4c09ccd

Browse files
committed
Check that Symbol properties are proper, and support downlevel type checking
1 parent 3834edd commit 4c09ccd

32 files changed

+457
-27
lines changed

Diff for: src/compiler/checker.ts

+48-27
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ module ts {
9696
var globalRegExpType: ObjectType;
9797
var globalTemplateStringsArrayType: ObjectType;
9898
var globalESSymbolType: ObjectType;
99-
var globalESSymbolConstructorType: ObjectType;
10099

101100
var anyArrayType: Type;
102101

@@ -3032,6 +3031,10 @@ module ts {
30323031
return getTypeOfGlobalSymbol(getGlobalTypeSymbol(name), 0);
30333032
}
30343033

3034+
function getGlobalESSymbolConstructorSymbol() {
3035+
return globalESSymbolConstructorSymbol || (globalESSymbolConstructorSymbol = getGlobalValueSymbol("Symbol"));
3036+
}
3037+
30353038
function createArrayType(elementType: Type): Type {
30363039
// globalArrayType will be undefined if we get here during creation of the Array type. This for example happens if
30373040
// user code augments the Array type with call or construct signatures that have an array type as the return type.
@@ -5541,12 +5544,7 @@ module ts {
55415544
error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any);
55425545
}
55435546
else if (isWellKnownSymbolSyntactically(node.expression)) {
5544-
// If it's not ES6, error
5545-
// Check that Symbol corresponds to global symbol, and that property exists, and that it's a symbol
5546-
}
5547-
else {
5548-
// Some syntactic forms require that the property name be a well known symbol.
5549-
// Will move the grammar checks here.
5547+
checkSymbolNameIsProperSymbolReference(<PropertyAccessExpression>node.expression, links.resolvedType, /*reportError*/ true);
55505548
}
55515549
}
55525550

@@ -5795,7 +5793,7 @@ module ts {
57955793

57965794
// See if we can index as a property.
57975795
if (node.argumentExpression) {
5798-
var name = getPropertyNameForIndexedAccess(node.argumentExpression);
5796+
var name = getPropertyNameForIndexedAccess(node.argumentExpression, indexType);
57995797
if (name !== undefined) {
58005798
var prop = getPropertyOfType(objectType, name);
58015799
if (prop) {
@@ -5846,12 +5844,12 @@ module ts {
58465844
* to this symbol, as long as it is a proper symbol reference.
58475845
* Otherwise, returns undefined.
58485846
*/
5849-
function getPropertyNameForIndexedAccess(indexArgumentExpression: Expression): string {
5847+
function getPropertyNameForIndexedAccess(indexArgumentExpression: Expression, indexArgumentType: Type): string {
58505848
if (indexArgumentExpression.kind === SyntaxKind.StringLiteral || indexArgumentExpression.kind === SyntaxKind.NumericLiteral) {
58515849
return (<LiteralExpression>indexArgumentExpression).text;
58525850
}
5853-
if (languageVersion >= ScriptTarget.ES6 && isWellKnownSymbolSyntactically(indexArgumentExpression)) {
5854-
if (checkSymbolNameIsProperSymbolReference(<PropertyAccessExpression>indexArgumentExpression, /*reportError*/ false)) {
5851+
if (isWellKnownSymbolSyntactically(indexArgumentExpression)) {
5852+
if (checkSymbolNameIsProperSymbolReference(<PropertyAccessExpression>indexArgumentExpression, indexArgumentType, /*reportError*/ false)) {
58555853
var rightHandSideName = (<Identifier>(<PropertyAccessExpression>indexArgumentExpression).name).text;
58565854
return getPropertyNameForKnownSymbolName(rightHandSideName);
58575855
}
@@ -5862,31 +5860,52 @@ module ts {
58625860

58635861
/**
58645862
* A proper symbol reference requires the following:
5865-
* 1. The language version is at least ES6
5866-
* 2. The expression is of the form Symbol.<identifier>
5867-
* 3. Symbol in this context resolves to the global Symbol object
5868-
* 4. The property access denotes a property that is present on the global Symbol object
5869-
* 5. The property on the global Symbol object is of the primitive type symbol.
5863+
* 1. The expression is of the form Symbol.<identifier>
5864+
* 2. Symbol in this context resolves to the global Symbol object
5865+
* 3. The property access denotes a property that is present on the global Symbol object
5866+
* 4. The property on the global Symbol object is of the primitive type symbol.
58705867
*/
5871-
function checkSymbolNameIsProperSymbolReference(wellKnownSymbolName: PropertyAccessExpression, reportError: boolean): boolean {
5872-
if (languageVersion < ScriptTarget.ES6) {
5868+
function checkSymbolNameIsProperSymbolReference(wellKnownSymbolName: PropertyAccessExpression, propertyNameType: Type, reportError: boolean): boolean {
5869+
if (propertyNameType === unknownType) {
5870+
// There is already an error, so no need to report one.
58735871
return false;
58745872
}
58755873

58765874
Debug.assert(isWellKnownSymbolSyntactically(wellKnownSymbolName));
5875+
5876+
// Make sure the property type is the primitive symbol type
5877+
if ((propertyNameType.flags & TypeFlags.ESSymbol) === 0) {
5878+
if (reportError) {
5879+
error(wellKnownSymbolName, Diagnostics.A_computed_property_name_of_the_form_0_must_be_of_type_symbol, getTextOfNode(wellKnownSymbolName));
5880+
}
5881+
return false;
5882+
}
5883+
58775884
// The name is Symbol.<someName>, so make sure Symbol actually resolves to the
58785885
// global Symbol object
58795886
var leftHandSide = (<PropertyAccessExpression>wellKnownSymbolName).expression;
5887+
// Look up the global symbol, but don't report an error, since checking the actual expression
5888+
// would have reported an error.
58805889
var leftHandSideSymbol = resolveName(wellKnownSymbolName, (<Identifier>leftHandSide).text,
58815890
SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined);
5882-
if (leftHandSideSymbol !== globalESSymbolConstructorSymbol) {
5891+
if (!leftHandSideSymbol) {
58835892
return false;
58845893
}
58855894

5886-
// Make sure the property type is the primitive symbol type
5887-
var rightHandSideName = (<Identifier>(<PropertyAccessExpression>wellKnownSymbolName).name).text;
5888-
var esSymbolConstructorPropertyType = getTypeOfPropertyOfType(globalESSymbolConstructorType, rightHandSideName);
5889-
return !!(esSymbolConstructorPropertyType && esSymbolConstructorPropertyType.flags & TypeFlags.ESSymbol);
5895+
var globalESSymbol = getGlobalESSymbolConstructorSymbol();
5896+
if (!globalESSymbol) {
5897+
// Already errored when we tried to look up the symbol
5898+
return false;
5899+
}
5900+
5901+
if (leftHandSideSymbol !== globalESSymbol) {
5902+
if (reportError) {
5903+
error(leftHandSide, Diagnostics.Symbol_reference_does_not_refer_to_the_global_Symbol_constructor_object);
5904+
}
5905+
return false;
5906+
}
5907+
5908+
return true;
58905909
}
58915910

58925911
function resolveUntypedCall(node: CallLikeExpression): Signature {
@@ -10391,13 +10410,15 @@ module ts {
1039110410
globalTemplateStringsArrayType = getGlobalType("TemplateStringsArray");
1039210411
globalESSymbolType = getGlobalType("Symbol");
1039310412
globalESSymbolConstructorSymbol = getGlobalValueSymbol("Symbol");
10394-
globalESSymbolConstructorType = getTypeOfSymbol(globalESSymbolConstructorSymbol);
1039510413
}
1039610414
else {
1039710415
globalTemplateStringsArrayType = unknownType;
10398-
globalESSymbolType = unknownType;
10399-
globalESSymbolConstructorSymbol = unknownSymbol;
10400-
globalESSymbolConstructorType = unknownType;
10416+
10417+
// Consider putting Symbol interface in lib.d.ts. On the plus side, putting it in lib.d.ts would make it
10418+
// extensible for Polyfilling Symbols. But putting it into lib.d.ts could also break users that have
10419+
// a global Symbol already, particularly if it is a class.
10420+
globalESSymbolType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
10421+
globalESSymbolConstructorSymbol = undefined;
1040110422
}
1040210423

1040310424
anyArrayType = createArrayType(anyType);

Diff for: src/compiler/diagnosticInformationMap.generated.ts

+2
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ module ts {
306306
A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type: { code: 2467, category: DiagnosticCategory.Error, key: "A computed property name cannot reference a type parameter from its containing type." },
307307
Cannot_find_global_value_0: { code: 2468, category: DiagnosticCategory.Error, key: "Cannot find global value '{0}'." },
308308
The_0_operator_cannot_be_applied_to_a_value_of_type_symbol: { code: 2469, category: DiagnosticCategory.Error, key: "The '{0}' operator cannot be applied to a value of type 'symbol'." },
309+
Symbol_reference_does_not_refer_to_the_global_Symbol_constructor_object: { code: 2470, category: DiagnosticCategory.Error, key: "'Symbol' reference does not refer to the global Symbol constructor object." },
310+
A_computed_property_name_of_the_form_0_must_be_of_type_symbol: { code: 2471, category: DiagnosticCategory.Error, key: "A computed property name of the form '{0}' must be of type 'symbol'." },
309311
Import_declaration_0_is_using_private_name_1: { code: 4000, category: DiagnosticCategory.Error, key: "Import declaration '{0}' is using private name '{1}'." },
310312
Type_parameter_0_of_exported_class_has_or_is_using_private_name_1: { code: 4002, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported class has or is using private name '{1}'." },
311313
Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1: { code: 4004, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported interface has or is using private name '{1}'." },

Diff for: src/compiler/diagnosticMessages.json

+8
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,14 @@
12171217
"category": "Error",
12181218
"code": 2469
12191219
},
1220+
"'Symbol' reference does not refer to the global Symbol constructor object.": {
1221+
"category": "Error",
1222+
"code": 2470
1223+
},
1224+
"A computed property name of the form '{0}' must be of type 'symbol'.": {
1225+
"category": "Error",
1226+
"code": 2471
1227+
},
12201228

12211229
"Import declaration '{0}' is using private name '{1}'.": {
12221230
"category": "Error",
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
tests/cases/conformance/Symbols/ES5SymbolProperty1.ts(7,5): error TS1167: Computed property names are only available when targeting ECMAScript 6 and higher.
2+
tests/cases/conformance/Symbols/ES5SymbolProperty1.ts(7,6): error TS2471: A computed property name of the form 'Symbol.foo' must be of type 'symbol'.
3+
4+
5+
==== tests/cases/conformance/Symbols/ES5SymbolProperty1.ts (2 errors) ====
6+
interface SymbolConstructor {
7+
foo: string;
8+
}
9+
var Symbol: SymbolConstructor;
10+
11+
var obj = {
12+
[Symbol.foo]: 0
13+
~~~~~~~~~~~~
14+
!!! error TS1167: Computed property names are only available when targeting ECMAScript 6 and higher.
15+
~~~~~~~~~~
16+
!!! error TS2471: A computed property name of the form 'Symbol.foo' must be of type 'symbol'.
17+
}
18+
19+
obj[Symbol.foo];

Diff for: tests/baselines/reference/ES5SymbolProperty1.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//// [ES5SymbolProperty1.ts]
2+
interface SymbolConstructor {
3+
foo: string;
4+
}
5+
var Symbol: SymbolConstructor;
6+
7+
var obj = {
8+
[Symbol.foo]: 0
9+
}
10+
11+
obj[Symbol.foo];
12+
13+
//// [ES5SymbolProperty1.js]
14+
var Symbol;
15+
var obj = {
16+
[Symbol.foo]: 0
17+
};
18+
obj[Symbol.foo];
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
tests/cases/conformance/Symbols/ES5SymbolProperty2.ts(5,9): error TS1167: Computed property names are only available when targeting ECMAScript 6 and higher.
2+
tests/cases/conformance/Symbols/ES5SymbolProperty2.ts(5,10): error TS2471: A computed property name of the form 'Symbol.iterator' must be of type 'symbol'.
3+
tests/cases/conformance/Symbols/ES5SymbolProperty2.ts(10,11): error TS2304: Cannot find name 'Symbol'.
4+
5+
6+
==== tests/cases/conformance/Symbols/ES5SymbolProperty2.ts (3 errors) ====
7+
module M {
8+
var Symbol;
9+
10+
export class C {
11+
[Symbol.iterator]() { }
12+
~~~~~~~~~~~~~~~~~
13+
!!! error TS1167: Computed property names are only available when targeting ECMAScript 6 and higher.
14+
~~~~~~~~~~~~~~~
15+
!!! error TS2471: A computed property name of the form 'Symbol.iterator' must be of type 'symbol'.
16+
}
17+
(new C)[Symbol.iterator];
18+
}
19+
20+
(new M.C)[Symbol.iterator];
21+
~~~~~~
22+
!!! error TS2304: Cannot find name 'Symbol'.

Diff for: tests/baselines/reference/ES5SymbolProperty2.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//// [ES5SymbolProperty2.ts]
2+
module M {
3+
var Symbol;
4+
5+
export class C {
6+
[Symbol.iterator]() { }
7+
}
8+
(new C)[Symbol.iterator];
9+
}
10+
11+
(new M.C)[Symbol.iterator];
12+
13+
//// [ES5SymbolProperty2.js]
14+
var M;
15+
(function (M) {
16+
var Symbol;
17+
var C = (function () {
18+
function C() {
19+
}
20+
C.prototype[Symbol.iterator] = function () {
21+
};
22+
return C;
23+
})();
24+
M.C = C;
25+
(new C)[Symbol.iterator];
26+
})(M || (M = {}));
27+
(new M.C)[Symbol.iterator];
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
tests/cases/conformance/Symbols/ES5SymbolProperty3.ts(4,5): error TS1167: Computed property names are only available when targeting ECMAScript 6 and higher.
2+
tests/cases/conformance/Symbols/ES5SymbolProperty3.ts(4,6): error TS2471: A computed property name of the form 'Symbol.iterator' must be of type 'symbol'.
3+
4+
5+
==== tests/cases/conformance/Symbols/ES5SymbolProperty3.ts (2 errors) ====
6+
var Symbol;
7+
8+
class C {
9+
[Symbol.iterator]() { }
10+
~~~~~~~~~~~~~~~~~
11+
!!! error TS1167: Computed property names are only available when targeting ECMAScript 6 and higher.
12+
~~~~~~~~~~~~~~~
13+
!!! error TS2471: A computed property name of the form 'Symbol.iterator' must be of type 'symbol'.
14+
}
15+
16+
(new C)[Symbol.iterator]

Diff for: tests/baselines/reference/ES5SymbolProperty3.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//// [ES5SymbolProperty3.ts]
2+
var Symbol;
3+
4+
class C {
5+
[Symbol.iterator]() { }
6+
}
7+
8+
(new C)[Symbol.iterator]
9+
10+
//// [ES5SymbolProperty3.js]
11+
var Symbol;
12+
var C = (function () {
13+
function C() {
14+
}
15+
C.prototype[Symbol.iterator] = function () {
16+
};
17+
return C;
18+
})();
19+
(new C)[Symbol.iterator];
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
tests/cases/conformance/Symbols/ES5SymbolProperty4.ts(4,5): error TS1167: Computed property names are only available when targeting ECMAScript 6 and higher.
2+
tests/cases/conformance/Symbols/ES5SymbolProperty4.ts(4,6): error TS2471: A computed property name of the form 'Symbol.iterator' must be of type 'symbol'.
3+
4+
5+
==== tests/cases/conformance/Symbols/ES5SymbolProperty4.ts (2 errors) ====
6+
var Symbol: { iterator: string };
7+
8+
class C {
9+
[Symbol.iterator]() { }
10+
~~~~~~~~~~~~~~~~~
11+
!!! error TS1167: Computed property names are only available when targeting ECMAScript 6 and higher.
12+
~~~~~~~~~~~~~~~
13+
!!! error TS2471: A computed property name of the form 'Symbol.iterator' must be of type 'symbol'.
14+
}
15+
16+
(new C)[Symbol.iterator]

Diff for: tests/baselines/reference/ES5SymbolProperty4.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//// [ES5SymbolProperty4.ts]
2+
var Symbol: { iterator: string };
3+
4+
class C {
5+
[Symbol.iterator]() { }
6+
}
7+
8+
(new C)[Symbol.iterator]
9+
10+
//// [ES5SymbolProperty4.js]
11+
var Symbol;
12+
var C = (function () {
13+
function C() {
14+
}
15+
C.prototype[Symbol.iterator] = function () {
16+
};
17+
return C;
18+
})();
19+
(new C)[Symbol.iterator];
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
tests/cases/conformance/Symbols/ES5SymbolProperty5.ts(4,5): error TS1167: Computed property names are only available when targeting ECMAScript 6 and higher.
2+
tests/cases/conformance/Symbols/ES5SymbolProperty5.ts(7,1): error TS2346: Supplied parameters do not match any signature of call target.
3+
4+
5+
==== tests/cases/conformance/Symbols/ES5SymbolProperty5.ts (2 errors) ====
6+
var Symbol: { iterator: symbol };
7+
8+
class C {
9+
[Symbol.iterator]() { }
10+
~~~~~~~~~~~~~~~~~
11+
!!! error TS1167: Computed property names are only available when targeting ECMAScript 6 and higher.
12+
}
13+
14+
(new C)[Symbol.iterator](0) // Should error
15+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
16+
!!! error TS2346: Supplied parameters do not match any signature of call target.

Diff for: tests/baselines/reference/ES5SymbolProperty5.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//// [ES5SymbolProperty5.ts]
2+
var Symbol: { iterator: symbol };
3+
4+
class C {
5+
[Symbol.iterator]() { }
6+
}
7+
8+
(new C)[Symbol.iterator](0) // Should error
9+
10+
//// [ES5SymbolProperty5.js]
11+
var Symbol;
12+
var C = (function () {
13+
function C() {
14+
}
15+
C.prototype[Symbol.iterator] = function () {
16+
};
17+
return C;
18+
})();
19+
(new C)[Symbol.iterator](0); // Should error
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
tests/cases/conformance/Symbols/ES5SymbolProperty6.ts(2,5): error TS1167: Computed property names are only available when targeting ECMAScript 6 and higher.
2+
tests/cases/conformance/Symbols/ES5SymbolProperty6.ts(2,6): error TS2304: Cannot find name 'Symbol'.
3+
tests/cases/conformance/Symbols/ES5SymbolProperty6.ts(5,9): error TS2304: Cannot find name 'Symbol'.
4+
5+
6+
==== tests/cases/conformance/Symbols/ES5SymbolProperty6.ts (3 errors) ====
7+
class C {
8+
[Symbol.iterator]() { }
9+
~~~~~~~~~~~~~~~~~
10+
!!! error TS1167: Computed property names are only available when targeting ECMAScript 6 and higher.
11+
~~~~~~
12+
!!! error TS2304: Cannot find name 'Symbol'.
13+
}
14+
15+
(new C)[Symbol.iterator]
16+
~~~~~~
17+
!!! error TS2304: Cannot find name 'Symbol'.

0 commit comments

Comments
 (0)