From 6798bd576bcc9a4c5c8f8fd8d25cd53a55ad085a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 15 Oct 2015 09:45:38 -0700 Subject: [PATCH 1/3] Primitives are not assignable to any-type indexers `string/numberIndexTypesRelatedTo` needs to prevent primitives from being assignable to an indexer of type 'any'. However, these two functions take an apparent type, which no longer has the primitive flag set. I thought of three ways to provide this information: 1. Pass the original type into `string/numberIndexTypesRelatedTo` and check its flag. 2. Record a boolean `isPrimitive` before converting to the apparent type, and pass it to `string/numberIndexTypesRelatedTo`. 3. Create a helper function `isPrimitive` that takes the apparent type and compares it to globalString/Number/Boolean/ESSymbolType. I decided on (1) because it seems like the simplest and safest. But none of the options are elegant. Please suggest improvements. --- src/compiler/checker.ts | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b13520cee2d..20ef9160333 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4895,7 +4895,7 @@ namespace ts { if (apparentType.flags & (TypeFlags.ObjectType | TypeFlags.Intersection) && target.flags & TypeFlags.ObjectType) { // Report structural errors only if we haven't reported any errors yet let reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo; - if (result = objectTypeRelatedTo(apparentType, target, reportStructuralErrors)) { + if (result = objectTypeRelatedTo(apparentType, source, target, reportStructuralErrors)) { errorInfo = saveErrorInfo; return result; } @@ -4917,7 +4917,7 @@ namespace ts { return result; } } - return objectTypeRelatedTo(source, target, /*reportErrors*/ false); + return objectTypeRelatedTo(source, source, target, /*reportErrors*/ false); } if (source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.TypeParameter) { return typeParameterIdenticalTo(source, target); @@ -5071,11 +5071,11 @@ namespace ts { // Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion // and issue an error. Otherwise, actually compare the structure of the two types. - function objectTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary { + function objectTypeRelatedTo(apparentSource: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary { if (overflow) { return Ternary.False; } - let id = relation !== identityRelation || source.id < target.id ? source.id + "," + target.id : target.id + "," + source.id; + let id = relation !== identityRelation || apparentSource.id < target.id ? apparentSource.id + "," + target.id : target.id + "," + apparentSource.id; let related = relation[id]; if (related !== undefined) { // If we computed this relation already and it was failed and reported, or if we're not being asked to elaborate @@ -5102,28 +5102,28 @@ namespace ts { maybeStack = []; expandingFlags = 0; } - sourceStack[depth] = source; + sourceStack[depth] = apparentSource; targetStack[depth] = target; maybeStack[depth] = {}; maybeStack[depth][id] = RelationComparisonResult.Succeeded; depth++; let saveExpandingFlags = expandingFlags; - if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack, depth)) expandingFlags |= 1; + if (!(expandingFlags & 1) && isDeeplyNestedGeneric(apparentSource, sourceStack, depth)) expandingFlags |= 1; if (!(expandingFlags & 2) && isDeeplyNestedGeneric(target, targetStack, depth)) expandingFlags |= 2; let result: Ternary; if (expandingFlags === 3) { result = Ternary.Maybe; } else { - result = propertiesRelatedTo(source, target, reportErrors); + result = propertiesRelatedTo(apparentSource, target, reportErrors); if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportErrors); + result &= signaturesRelatedTo(apparentSource, target, SignatureKind.Call, reportErrors); if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportErrors); + result &= signaturesRelatedTo(apparentSource, target, SignatureKind.Construct, reportErrors); if (result) { - result &= stringIndexTypesRelatedTo(source, target, reportErrors); + result &= stringIndexTypesRelatedTo(apparentSource, originalSource, target, reportErrors); if (result) { - result &= numberIndexTypesRelatedTo(source, target, reportErrors); + result &= numberIndexTypesRelatedTo(apparentSource, originalSource, target, reportErrors); } } } @@ -5454,12 +5454,17 @@ namespace ts { return result; } - function stringIndexTypesRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary { + function stringIndexTypesRelatedTo(source: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary { if (relation === identityRelation) { return indexTypesIdenticalTo(IndexKind.String, source, target); } let targetType = getIndexTypeOfType(target, IndexKind.String); - if (targetType && !(targetType.flags & TypeFlags.Any)) { + if (targetType) { + if ((targetType.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive)) { + // non-primitive assignment to any is always allowed, eg + // `var x: { [index: string]: any } = { property: 12 };` + return Ternary.True; + } let sourceType = getIndexTypeOfType(source, IndexKind.String); if (!sourceType) { if (reportErrors) { @@ -5479,12 +5484,17 @@ namespace ts { return Ternary.True; } - function numberIndexTypesRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary { + function numberIndexTypesRelatedTo(source: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary { if (relation === identityRelation) { return indexTypesIdenticalTo(IndexKind.Number, source, target); } let targetType = getIndexTypeOfType(target, IndexKind.Number); - if (targetType && !(targetType.flags & TypeFlags.Any)) { + if (targetType) { + if ((targetType.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive)) { + // non-primitive assignment to any is always allowed, eg + // `var x: { [index: number]: any } = { property: 12 };` + return Ternary.True; + } let sourceStringType = getIndexTypeOfType(source, IndexKind.String); let sourceNumberType = getIndexTypeOfType(source, IndexKind.Number); if (!(sourceStringType || sourceNumberType)) { From 8eacd41ab0300259c4837702e0dfddf22bd22e8a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 15 Oct 2015 09:52:31 -0700 Subject: [PATCH 2/3] Add tests and accept baselines --- .../baselines/reference/assignmentCompat1.errors.txt | 10 +++++++++- tests/baselines/reference/assignmentCompat1.js | 5 +++++ tests/baselines/reference/indexTypeCheck.errors.txt | 5 ++++- tests/baselines/reference/intTypeCheck.errors.txt | 12 +++++++++++- tests/cases/compiler/assignmentCompat1.ts | 3 +++ 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/tests/baselines/reference/assignmentCompat1.errors.txt b/tests/baselines/reference/assignmentCompat1.errors.txt index edb8d28eecc..93afdbe1bcb 100644 --- a/tests/baselines/reference/assignmentCompat1.errors.txt +++ b/tests/baselines/reference/assignmentCompat1.errors.txt @@ -2,9 +2,11 @@ tests/cases/compiler/assignmentCompat1.ts(4,1): error TS2322: Type '{ [index: st Property 'one' is missing in type '{ [index: string]: any; }'. tests/cases/compiler/assignmentCompat1.ts(6,1): error TS2322: Type '{ [index: number]: any; }' is not assignable to type '{ one: number; }'. Property 'one' is missing in type '{ [index: number]: any; }'. +tests/cases/compiler/assignmentCompat1.ts(8,1): error TS2322: Type 'string' is not assignable to type '{ [index: string]: any; }'. + Index signature is missing in type 'String'. -==== tests/cases/compiler/assignmentCompat1.ts (2 errors) ==== +==== tests/cases/compiler/assignmentCompat1.ts (3 errors) ==== var x = { one: 1 }; var y: { [index: string]: any }; var z: { [index: number]: any }; @@ -18,4 +20,10 @@ tests/cases/compiler/assignmentCompat1.ts(6,1): error TS2322: Type '{ [index: nu !!! error TS2322: Type '{ [index: number]: any; }' is not assignable to type '{ one: number; }'. !!! error TS2322: Property 'one' is missing in type '{ [index: number]: any; }'. z = x; // Ok because index signature type is any + y = "foo"; // Error + ~ +!!! error TS2322: Type 'string' is not assignable to type '{ [index: string]: any; }'. +!!! error TS2322: Index signature is missing in type 'String'. + z = "foo"; // Error + \ No newline at end of file diff --git a/tests/baselines/reference/assignmentCompat1.js b/tests/baselines/reference/assignmentCompat1.js index 740a44cced4..a873c24c567 100644 --- a/tests/baselines/reference/assignmentCompat1.js +++ b/tests/baselines/reference/assignmentCompat1.js @@ -6,6 +6,9 @@ x = y; // Error y = x; // Ok because index signature type is any x = z; // Error z = x; // Ok because index signature type is any +y = "foo"; // Error +z = "foo"; // Error + //// [assignmentCompat1.js] @@ -16,3 +19,5 @@ x = y; // Error y = x; // Ok because index signature type is any x = z; // Error z = x; // Ok because index signature type is any +y = "foo"; // Error +z = "foo"; // Error diff --git a/tests/baselines/reference/indexTypeCheck.errors.txt b/tests/baselines/reference/indexTypeCheck.errors.txt index 2ea5a2f413a..06b844f8321 100644 --- a/tests/baselines/reference/indexTypeCheck.errors.txt +++ b/tests/baselines/reference/indexTypeCheck.errors.txt @@ -1,13 +1,14 @@ tests/cases/compiler/indexTypeCheck.ts(2,2): error TS1021: An index signature must have a type annotation. tests/cases/compiler/indexTypeCheck.ts(3,2): error TS1021: An index signature must have a type annotation. tests/cases/compiler/indexTypeCheck.ts(17,2): error TS2413: Numeric index type 'number' is not assignable to string index type 'string'. +tests/cases/compiler/indexTypeCheck.ts(22,2): error TS2413: Numeric index type 'Orange' is not assignable to string index type 'Yellow'. tests/cases/compiler/indexTypeCheck.ts(27,2): error TS2413: Numeric index type 'number' is not assignable to string index type 'string'. tests/cases/compiler/indexTypeCheck.ts(32,3): error TS1096: An index signature must have exactly one parameter. tests/cases/compiler/indexTypeCheck.ts(36,3): error TS1023: An index signature parameter type must be 'string' or 'number'. tests/cases/compiler/indexTypeCheck.ts(51,1): error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. -==== tests/cases/compiler/indexTypeCheck.ts (7 errors) ==== +==== tests/cases/compiler/indexTypeCheck.ts (8 errors) ==== interface Red { [n:number]; // ok ~~~~~~~~~~~ @@ -36,6 +37,8 @@ tests/cases/compiler/indexTypeCheck.ts(51,1): error TS2342: An index expression interface Green { [n:number]: Orange; // error + ~~~~~~~~~~~~~~~~~~~ +!!! error TS2413: Numeric index type 'Orange' is not assignable to string index type 'Yellow'. [s:string]: Yellow; // ok } diff --git a/tests/baselines/reference/intTypeCheck.errors.txt b/tests/baselines/reference/intTypeCheck.errors.txt index 7f1cc88e2a2..e48d1b1dc73 100644 --- a/tests/baselines/reference/intTypeCheck.errors.txt +++ b/tests/baselines/reference/intTypeCheck.errors.txt @@ -30,6 +30,8 @@ tests/cases/compiler/intTypeCheck.ts(134,21): error TS1109: Expression expected. tests/cases/compiler/intTypeCheck.ts(134,22): error TS2304: Cannot find name 'i3'. tests/cases/compiler/intTypeCheck.ts(135,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. tests/cases/compiler/intTypeCheck.ts(142,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. +tests/cases/compiler/intTypeCheck.ts(148,5): error TS2322: Type 'boolean' is not assignable to type 'i4'. + Index signature is missing in type 'Boolean'. tests/cases/compiler/intTypeCheck.ts(148,21): error TS1109: Expression expected. tests/cases/compiler/intTypeCheck.ts(148,22): error TS2304: Cannot find name 'i4'. tests/cases/compiler/intTypeCheck.ts(149,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. @@ -66,12 +68,14 @@ tests/cases/compiler/intTypeCheck.ts(190,21): error TS1109: Expression expected. tests/cases/compiler/intTypeCheck.ts(190,22): error TS2304: Cannot find name 'i7'. tests/cases/compiler/intTypeCheck.ts(191,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. tests/cases/compiler/intTypeCheck.ts(198,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. +tests/cases/compiler/intTypeCheck.ts(204,5): error TS2322: Type 'boolean' is not assignable to type 'i8'. + Index signature is missing in type 'Boolean'. tests/cases/compiler/intTypeCheck.ts(204,21): error TS1109: Expression expected. tests/cases/compiler/intTypeCheck.ts(204,22): error TS2304: Cannot find name 'i8'. tests/cases/compiler/intTypeCheck.ts(205,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. -==== tests/cases/compiler/intTypeCheck.ts (61 errors) ==== +==== tests/cases/compiler/intTypeCheck.ts (63 errors) ==== interface i1 { //Property Signatures p; @@ -280,6 +284,9 @@ tests/cases/compiler/intTypeCheck.ts(205,17): error TS2351: Cannot use 'new' wit //var obj40: i4 = function foo() { }; var obj41: i4 = anyVar; var obj42: i4 = new anyVar; + ~~~~~ +!!! error TS2322: Type 'boolean' is not assignable to type 'i4'. +!!! error TS2322: Index signature is missing in type 'Boolean'. ~ !!! error TS1109: Expression expected. ~~ @@ -402,6 +409,9 @@ tests/cases/compiler/intTypeCheck.ts(205,17): error TS2351: Cannot use 'new' wit //var obj84: i8 = function foo() { }; var obj85: i8 = anyVar; var obj86: i8 = new anyVar; + ~~~~~ +!!! error TS2322: Type 'boolean' is not assignable to type 'i8'. +!!! error TS2322: Index signature is missing in type 'Boolean'. ~ !!! error TS1109: Expression expected. ~~ diff --git a/tests/cases/compiler/assignmentCompat1.ts b/tests/cases/compiler/assignmentCompat1.ts index b37a11b20d9..54dc91adab0 100644 --- a/tests/cases/compiler/assignmentCompat1.ts +++ b/tests/cases/compiler/assignmentCompat1.ts @@ -5,3 +5,6 @@ x = y; // Error y = x; // Ok because index signature type is any x = z; // Error z = x; // Ok because index signature type is any +y = "foo"; // Error +z = "foo"; // Error + From 5cd0ca19af72f1cdeebf16e3310bf746a567afce Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 15 Oct 2015 11:04:36 -0700 Subject: [PATCH 3/3] Add test case, correct existing test case Existing: String assignment to a numeric indexer should succeed, not fail. (The baseline was already correct but the inline comment was wrong.) New: Boolean assignment to a numeric indexer should fail. --- tests/baselines/reference/assignmentCompat1.errors.txt | 10 ++++++++-- tests/baselines/reference/assignmentCompat1.js | 6 ++++-- tests/cases/compiler/assignmentCompat1.ts | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/baselines/reference/assignmentCompat1.errors.txt b/tests/baselines/reference/assignmentCompat1.errors.txt index 93afdbe1bcb..7539af92887 100644 --- a/tests/baselines/reference/assignmentCompat1.errors.txt +++ b/tests/baselines/reference/assignmentCompat1.errors.txt @@ -4,9 +4,11 @@ tests/cases/compiler/assignmentCompat1.ts(6,1): error TS2322: Type '{ [index: nu Property 'one' is missing in type '{ [index: number]: any; }'. tests/cases/compiler/assignmentCompat1.ts(8,1): error TS2322: Type 'string' is not assignable to type '{ [index: string]: any; }'. Index signature is missing in type 'String'. +tests/cases/compiler/assignmentCompat1.ts(10,1): error TS2322: Type 'boolean' is not assignable to type '{ [index: number]: any; }'. + Index signature is missing in type 'Boolean'. -==== tests/cases/compiler/assignmentCompat1.ts (3 errors) ==== +==== tests/cases/compiler/assignmentCompat1.ts (4 errors) ==== var x = { one: 1 }; var y: { [index: string]: any }; var z: { [index: number]: any }; @@ -24,6 +26,10 @@ tests/cases/compiler/assignmentCompat1.ts(8,1): error TS2322: Type 'string' is n ~ !!! error TS2322: Type 'string' is not assignable to type '{ [index: string]: any; }'. !!! error TS2322: Index signature is missing in type 'String'. - z = "foo"; // Error + z = "foo"; // OK, string has numeric indexer + z = false; // Error + ~ +!!! error TS2322: Type 'boolean' is not assignable to type '{ [index: number]: any; }'. +!!! error TS2322: Index signature is missing in type 'Boolean'. \ No newline at end of file diff --git a/tests/baselines/reference/assignmentCompat1.js b/tests/baselines/reference/assignmentCompat1.js index a873c24c567..7451d342fa1 100644 --- a/tests/baselines/reference/assignmentCompat1.js +++ b/tests/baselines/reference/assignmentCompat1.js @@ -7,7 +7,8 @@ y = x; // Ok because index signature type is any x = z; // Error z = x; // Ok because index signature type is any y = "foo"; // Error -z = "foo"; // Error +z = "foo"; // OK, string has numeric indexer +z = false; // Error @@ -20,4 +21,5 @@ y = x; // Ok because index signature type is any x = z; // Error z = x; // Ok because index signature type is any y = "foo"; // Error -z = "foo"; // Error +z = "foo"; // OK, string has numeric indexer +z = false; // Error diff --git a/tests/cases/compiler/assignmentCompat1.ts b/tests/cases/compiler/assignmentCompat1.ts index 54dc91adab0..e93da76d95e 100644 --- a/tests/cases/compiler/assignmentCompat1.ts +++ b/tests/cases/compiler/assignmentCompat1.ts @@ -6,5 +6,6 @@ y = x; // Ok because index signature type is any x = z; // Error z = x; // Ok because index signature type is any y = "foo"; // Error -z = "foo"; // Error +z = "foo"; // OK, string has numeric indexer +z = false; // Error