Check computed properties against indexers in classes

This commit is contained in:
Jason Freeman
2015-01-20 17:07:55 -08:00
parent 89f36bb9c5
commit 85219ee5e1
30 changed files with 734 additions and 12 deletions
+33 -12
View File
@@ -4767,6 +4767,7 @@ module ts {
return type;
}
}
/*Transitively mark all linked imports as referenced*/
function markLinkedImportsAsReferenced(node: ImportDeclaration): void {
var nodeLinks = getNodeLinks(node);
@@ -8466,41 +8467,46 @@ module ts {
if (node.finallyBlock) checkBlock(node.finallyBlock);
}
function checkIndexConstraints(type: Type) {
function checkIndexConstraints(type: Type) {
function checkIndexConstraintForProperty(
prop: Symbol,
propertyType: Type,
containingType: Type,
indexDeclaration: Declaration,
indexType: Type,
indexKind: IndexKind): void {
function checkIndexConstraintForProperty(prop: Symbol, propertyType: Type, indexDeclaration: Declaration, indexType: Type, indexKind: IndexKind): void {
if (!indexType) {
return;
}
// index is numeric and property name is not valid numeric literal
if (indexKind === IndexKind.Number && !isNumericLiteralName(prop.name)) {
if (indexKind === IndexKind.Number && !isNumericName(prop.valueDeclaration.name)) {
return;
}
// perform property check if property or indexer is declared in 'type'
// this allows to rule out cases when both property and indexer are inherited from the base class
var errorNode: Node;
if (prop.parent === type.symbol) {
if (prop.valueDeclaration.name.kind === SyntaxKind.ComputedPropertyName || prop.parent === containingType.symbol) {
errorNode = prop.valueDeclaration;
}
else if (indexDeclaration) {
errorNode = indexDeclaration;
}
else if (type.flags & TypeFlags.Interface) {
else if (containingType.flags & TypeFlags.Interface) {
// for interfaces property and indexer might be inherited from different bases
// check if any base class already has both property and indexer.
// check should be performed only if 'type' is the first type that brings property\indexer together
var someBaseClassHasBothPropertyAndIndexer = forEach((<InterfaceType>type).baseTypes, base => getPropertyOfObjectType(base, prop.name) && getIndexTypeOfType(base, indexKind));
errorNode = someBaseClassHasBothPropertyAndIndexer ? undefined : type.symbol.declarations[0];
var someBaseClassHasBothPropertyAndIndexer = forEach((<InterfaceType>containingType).baseTypes, base => getPropertyOfObjectType(base, prop.name) && getIndexTypeOfType(base, indexKind));
errorNode = someBaseClassHasBothPropertyAndIndexer ? undefined : containingType.symbol.declarations[0];
}
if (errorNode && !isTypeAssignableTo(propertyType, indexType)) {
var errorMessage =
indexKind === IndexKind.String
? Diagnostics.Property_0_of_type_1_is_not_assignable_to_string_index_type_2
: Diagnostics.Property_0_of_type_1_is_not_assignable_to_numeric_index_type_2;
? Diagnostics.Property_0_of_type_1_is_not_assignable_to_string_index_type_2
: Diagnostics.Property_0_of_type_1_is_not_assignable_to_numeric_index_type_2;
error(errorNode, errorMessage, symbolToString(prop), typeToString(propertyType), typeToString(indexType));
}
}
@@ -8514,9 +8520,24 @@ module ts {
if (stringIndexType || numberIndexType) {
forEach(getPropertiesOfObjectType(type), prop => {
var propType = getTypeOfSymbol(prop);
checkIndexConstraintForProperty(prop, propType, declaredStringIndexer, stringIndexType, IndexKind.String);
checkIndexConstraintForProperty(prop, propType, declaredNumberIndexer, numberIndexType, IndexKind.Number);
checkIndexConstraintForProperty(prop, propType, type, declaredStringIndexer, stringIndexType, IndexKind.String);
checkIndexConstraintForProperty(prop, propType, type, declaredNumberIndexer, numberIndexType, IndexKind.Number);
});
if (type.flags & TypeFlags.Class && type.symbol.valueDeclaration.kind === SyntaxKind.ClassDeclaration) {
var classDeclaration = <ClassDeclaration>type.symbol.valueDeclaration;
for (var i = 0; i < classDeclaration.members.length; i++) {
var member = classDeclaration.members[i];
// Only process instance properties with computed names here.
// Static properties cannot be in conflict with indexers,
// and properties with literal names were already checked.
if (!(member.flags & NodeFlags.Static) && hasComputedNameButNotSymbol(member)) {
var propType = getTypeOfSymbol(member.symbol);
checkIndexConstraintForProperty(member.symbol, propType, type, declaredStringIndexer, stringIndexType, IndexKind.String);
checkIndexConstraintForProperty(member.symbol, propType, type, declaredNumberIndexer, numberIndexType, IndexKind.Number);
}
}
}
}
var errorNode: Node;
@@ -0,0 +1,16 @@
tests/cases/conformance/es6/computedProperties/computedPropertyNames36.ts(8,5): error TS2411: Property '["get1"]' of type 'Foo' is not assignable to string index type 'Foo2'.
==== tests/cases/conformance/es6/computedProperties/computedPropertyNames36.ts (1 errors) ====
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
// Computed properties
get ["get1"]() { return new Foo }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2411: Property '["get1"]' of type 'Foo' is not assignable to string index type 'Foo2'.
set ["set1"](p: Foo2) { }
}
@@ -0,0 +1,42 @@
//// [computedPropertyNames36.ts]
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
// Computed properties
get ["get1"]() { return new Foo }
set ["set1"](p: Foo2) { }
}
//// [computedPropertyNames36.js]
var Foo = (function () {
function Foo() {
}
return Foo;
})();
var Foo2 = (function () {
function Foo2() {
}
return Foo2;
})();
var C = (function () {
function C() {
}
Object.defineProperty(C.prototype, "get1", {
// Computed properties
get: function () {
return new Foo;
},
enumerable: true,
configurable: true
});
Object.defineProperty(C.prototype, "set1", {
set: function (p) {
},
enumerable: true,
configurable: true
});
return C;
})();
@@ -0,0 +1,42 @@
//// [computedPropertyNames37.ts]
class Foo { x }
class Foo2 { x; y }
class C {
[s: number]: Foo2;
// Computed properties
get ["get1"]() { return new Foo }
set ["set1"](p: Foo2) { }
}
//// [computedPropertyNames37.js]
var Foo = (function () {
function Foo() {
}
return Foo;
})();
var Foo2 = (function () {
function Foo2() {
}
return Foo2;
})();
var C = (function () {
function C() {
}
Object.defineProperty(C.prototype, "get1", {
// Computed properties
get: function () {
return new Foo;
},
enumerable: true,
configurable: true
});
Object.defineProperty(C.prototype, "set1", {
set: function (p) {
},
enumerable: true,
configurable: true
});
return C;
})();
@@ -0,0 +1,26 @@
=== tests/cases/conformance/es6/computedProperties/computedPropertyNames37.ts ===
class Foo { x }
>Foo : Foo
>x : any
class Foo2 { x; y }
>Foo2 : Foo2
>x : any
>y : any
class C {
>C : C
[s: number]: Foo2;
>s : number
>Foo2 : Foo2
// Computed properties
get ["get1"]() { return new Foo }
>new Foo : Foo
>Foo : typeof Foo
set ["set1"](p: Foo2) { }
>p : Foo2
>Foo2 : Foo2
}
@@ -0,0 +1,16 @@
tests/cases/conformance/es6/computedProperties/computedPropertyNames38.ts(8,5): error TS2411: Property '[1 << 6]' of type 'Foo' is not assignable to string index type 'Foo2'.
==== tests/cases/conformance/es6/computedProperties/computedPropertyNames38.ts (1 errors) ====
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
// Computed properties
get [1 << 6]() { return new Foo }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2411: Property '[1 << 6]' of type 'Foo' is not assignable to string index type 'Foo2'.
set [1 << 6](p: Foo2) { }
}
@@ -0,0 +1,42 @@
//// [computedPropertyNames38.ts]
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
// Computed properties
get [1 << 6]() { return new Foo }
set [1 << 6](p: Foo2) { }
}
//// [computedPropertyNames38.js]
var Foo = (function () {
function Foo() {
}
return Foo;
})();
var Foo2 = (function () {
function Foo2() {
}
return Foo2;
})();
var C = (function () {
function C() {
}
Object.defineProperty(C.prototype, 1 << 6, {
// Computed properties
get: function () {
return new Foo;
},
enumerable: true,
configurable: true
});
Object.defineProperty(C.prototype, 1 << 6, {
set: function (p) {
},
enumerable: true,
configurable: true
});
return C;
})();
@@ -0,0 +1,16 @@
tests/cases/conformance/es6/computedProperties/computedPropertyNames39.ts(8,5): error TS2412: Property '[1 << 6]' of type 'Foo' is not assignable to numeric index type 'Foo2'.
==== tests/cases/conformance/es6/computedProperties/computedPropertyNames39.ts (1 errors) ====
class Foo { x }
class Foo2 { x; y }
class C {
[s: number]: Foo2;
// Computed properties
get [1 << 6]() { return new Foo }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2412: Property '[1 << 6]' of type 'Foo' is not assignable to numeric index type 'Foo2'.
set [1 << 6](p: Foo2) { }
}
@@ -0,0 +1,42 @@
//// [computedPropertyNames39.ts]
class Foo { x }
class Foo2 { x; y }
class C {
[s: number]: Foo2;
// Computed properties
get [1 << 6]() { return new Foo }
set [1 << 6](p: Foo2) { }
}
//// [computedPropertyNames39.js]
var Foo = (function () {
function Foo() {
}
return Foo;
})();
var Foo2 = (function () {
function Foo2() {
}
return Foo2;
})();
var C = (function () {
function C() {
}
Object.defineProperty(C.prototype, 1 << 6, {
// Computed properties
get: function () {
return new Foo;
},
enumerable: true,
configurable: true
});
Object.defineProperty(C.prototype, 1 << 6, {
set: function (p) {
},
enumerable: true,
configurable: true
});
return C;
})();
@@ -0,0 +1,16 @@
tests/cases/conformance/es6/computedProperties/computedPropertyNames40.ts(8,5): error TS2411: Property '[""]' of type '() => Foo' is not assignable to string index type '() => Foo2'.
==== tests/cases/conformance/es6/computedProperties/computedPropertyNames40.ts (1 errors) ====
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: () => Foo2;
// Computed properties
[""]() { return new Foo }
~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2411: Property '[""]' of type '() => Foo' is not assignable to string index type '() => Foo2'.
[""]() { return new Foo2 }
}
@@ -0,0 +1,35 @@
//// [computedPropertyNames40.ts]
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: () => Foo2;
// Computed properties
[""]() { return new Foo }
[""]() { return new Foo2 }
}
//// [computedPropertyNames40.js]
var Foo = (function () {
function Foo() {
}
return Foo;
})();
var Foo2 = (function () {
function Foo2() {
}
return Foo2;
})();
var C = (function () {
function C() {
}
// Computed properties
C.prototype[""] = function () {
return new Foo;
};
C.prototype[""] = function () {
return new Foo2;
};
return C;
})();
@@ -0,0 +1,31 @@
//// [computedPropertyNames41.ts]
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: () => Foo2;
// Computed properties
static [""]() { return new Foo }
}
//// [computedPropertyNames41.js]
var Foo = (function () {
function Foo() {
}
return Foo;
})();
var Foo2 = (function () {
function Foo2() {
}
return Foo2;
})();
var C = (function () {
function C() {
}
// Computed properties
C[""] = function () {
return new Foo;
};
return C;
})();
@@ -0,0 +1,22 @@
=== tests/cases/conformance/es6/computedProperties/computedPropertyNames41.ts ===
class Foo { x }
>Foo : Foo
>x : any
class Foo2 { x; y }
>Foo2 : Foo2
>x : any
>y : any
class C {
>C : C
[s: string]: () => Foo2;
>s : string
>Foo2 : Foo2
// Computed properties
static [""]() { return new Foo }
>new Foo : Foo
>Foo : typeof Foo
}
@@ -0,0 +1,18 @@
tests/cases/conformance/es6/computedProperties/computedPropertyNames42.ts(8,5): error TS1166: Computed property names are not allowed in class property declarations.
tests/cases/conformance/es6/computedProperties/computedPropertyNames42.ts(8,5): error TS2411: Property '[""]' of type 'Foo' is not assignable to string index type 'Foo2'.
==== tests/cases/conformance/es6/computedProperties/computedPropertyNames42.ts (2 errors) ====
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
// Computed properties
[""]: Foo;
~~~~
!!! error TS1166: Computed property names are not allowed in class property declarations.
~~~~~~~~~~
!!! error TS2411: Property '[""]' of type 'Foo' is not assignable to string index type 'Foo2'.
}
@@ -0,0 +1,18 @@
tests/cases/conformance/es6/computedProperties/computedPropertyNames43.ts(10,5): error TS2411: Property '["get1"]' of type 'Foo' is not assignable to string index type 'Foo2'.
==== tests/cases/conformance/es6/computedProperties/computedPropertyNames43.ts (1 errors) ====
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
}
class D extends C {
// Computed properties
get ["get1"]() { return new Foo }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2411: Property '["get1"]' of type 'Foo' is not assignable to string index type 'Foo2'.
set ["set1"](p: Foo2) { }
}
@@ -0,0 +1,57 @@
//// [computedPropertyNames43.ts]
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
}
class D extends C {
// Computed properties
get ["get1"]() { return new Foo }
set ["set1"](p: Foo2) { }
}
//// [computedPropertyNames43.js]
var __extends = this.__extends || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
var Foo = (function () {
function Foo() {
}
return Foo;
})();
var Foo2 = (function () {
function Foo2() {
}
return Foo2;
})();
var C = (function () {
function C() {
}
return C;
})();
var D = (function (_super) {
__extends(D, _super);
function D() {
_super.apply(this, arguments);
}
Object.defineProperty(D.prototype, "get1", {
// Computed properties
get: function () {
return new Foo;
},
enumerable: true,
configurable: true
});
Object.defineProperty(D.prototype, "set1", {
set: function (p) {
},
enumerable: true,
configurable: true
});
return D;
})(C);
@@ -0,0 +1,20 @@
tests/cases/conformance/es6/computedProperties/computedPropertyNames44.ts(6,5): error TS2411: Property '["get1"]' of type 'Foo' is not assignable to string index type 'Foo2'.
tests/cases/conformance/es6/computedProperties/computedPropertyNames44.ts(10,5): error TS2411: Property '["set1"]' of type 'Foo' is not assignable to string index type 'Foo2'.
==== tests/cases/conformance/es6/computedProperties/computedPropertyNames44.ts (2 errors) ====
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
get ["get1"]() { return new Foo }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2411: Property '["get1"]' of type 'Foo' is not assignable to string index type 'Foo2'.
}
class D extends C {
set ["set1"](p: Foo) { }
~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2411: Property '["set1"]' of type 'Foo' is not assignable to string index type 'Foo2'.
}
@@ -0,0 +1,55 @@
//// [computedPropertyNames44.ts]
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
get ["get1"]() { return new Foo }
}
class D extends C {
set ["set1"](p: Foo) { }
}
//// [computedPropertyNames44.js]
var __extends = this.__extends || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
var Foo = (function () {
function Foo() {
}
return Foo;
})();
var Foo2 = (function () {
function Foo2() {
}
return Foo2;
})();
var C = (function () {
function C() {
}
Object.defineProperty(C.prototype, "get1", {
get: function () {
return new Foo;
},
enumerable: true,
configurable: true
});
return C;
})();
var D = (function (_super) {
__extends(D, _super);
function D() {
_super.apply(this, arguments);
}
Object.defineProperty(D.prototype, "set1", {
set: function (p) {
},
enumerable: true,
configurable: true
});
return D;
})(C);
@@ -0,0 +1,18 @@
tests/cases/conformance/es6/computedProperties/computedPropertyNames45.ts(11,5): error TS2411: Property '["set1"]' of type 'Foo' is not assignable to string index type 'Foo2'.
==== tests/cases/conformance/es6/computedProperties/computedPropertyNames45.ts (1 errors) ====
class Foo { x }
class Foo2 { x; y }
class C {
get ["get1"]() { return new Foo }
}
class D extends C {
// No error when the indexer is in a class more derived than the computed property
[s: string]: Foo2;
set ["set1"](p: Foo) { }
~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2411: Property '["set1"]' of type 'Foo' is not assignable to string index type 'Foo2'.
}
@@ -0,0 +1,56 @@
//// [computedPropertyNames45.ts]
class Foo { x }
class Foo2 { x; y }
class C {
get ["get1"]() { return new Foo }
}
class D extends C {
// No error when the indexer is in a class more derived than the computed property
[s: string]: Foo2;
set ["set1"](p: Foo) { }
}
//// [computedPropertyNames45.js]
var __extends = this.__extends || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
var Foo = (function () {
function Foo() {
}
return Foo;
})();
var Foo2 = (function () {
function Foo2() {
}
return Foo2;
})();
var C = (function () {
function C() {
}
Object.defineProperty(C.prototype, "get1", {
get: function () {
return new Foo;
},
enumerable: true,
configurable: true
});
return C;
})();
var D = (function (_super) {
__extends(D, _super);
function D() {
_super.apply(this, arguments);
}
Object.defineProperty(D.prototype, "set1", {
set: function (p) {
},
enumerable: true,
configurable: true
});
return D;
})(C);
@@ -0,0 +1,11 @@
// @target: es6
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
// Computed properties
get ["get1"]() { return new Foo }
set ["set1"](p: Foo2) { }
}
@@ -0,0 +1,11 @@
// @target: es6
class Foo { x }
class Foo2 { x; y }
class C {
[s: number]: Foo2;
// Computed properties
get ["get1"]() { return new Foo }
set ["set1"](p: Foo2) { }
}
@@ -0,0 +1,11 @@
// @target: es6
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
// Computed properties
get [1 << 6]() { return new Foo }
set [1 << 6](p: Foo2) { }
}
@@ -0,0 +1,11 @@
// @target: es6
class Foo { x }
class Foo2 { x; y }
class C {
[s: number]: Foo2;
// Computed properties
get [1 << 6]() { return new Foo }
set [1 << 6](p: Foo2) { }
}
@@ -0,0 +1,11 @@
// @target: es6
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: () => Foo2;
// Computed properties
[""]() { return new Foo }
[""]() { return new Foo2 }
}
@@ -0,0 +1,10 @@
// @target: es6
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: () => Foo2;
// Computed properties
static [""]() { return new Foo }
}
@@ -0,0 +1,10 @@
// @target: es6
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
// Computed properties
[""]: Foo;
}
@@ -0,0 +1,13 @@
// @target: es6
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
}
class D extends C {
// Computed properties
get ["get1"]() { return new Foo }
set ["set1"](p: Foo2) { }
}
@@ -0,0 +1,12 @@
// @target: es6
class Foo { x }
class Foo2 { x; y }
class C {
[s: string]: Foo2;
get ["get1"]() { return new Foo }
}
class D extends C {
set ["set1"](p: Foo) { }
}
@@ -0,0 +1,13 @@
// @target: es6
class Foo { x }
class Foo2 { x; y }
class C {
get ["get1"]() { return new Foo }
}
class D extends C {
// No error when the indexer is in a class more derived than the computed property
[s: string]: Foo2;
set ["set1"](p: Foo) { }
}