diff --git a/src/utils/__tests__/__snapshots__/rttc.ts.snap b/src/utils/__tests__/__snapshots__/rttc.ts.snap index cd1cbab97..f0086e609 100644 --- a/src/utils/__tests__/__snapshots__/rttc.ts.snap +++ b/src/utils/__tests__/__snapshots__/rttc.ts.snap @@ -8317,8 +8317,8 @@ Object { exports[`Member expression type combinations: Invalid type combinations return TypeError 54`] = ` Object { - "elaborate": "Expected number as prop, got boolean.", - "explain": "Expected number as prop, got boolean.", + "elaborate": "Expected array index as prop, got boolean.", + "explain": "Expected array index as prop, got boolean.", "left": Array [ 2, ], @@ -8328,8 +8328,8 @@ Object { exports[`Member expression type combinations: Invalid type combinations return TypeError 55`] = ` Object { - "elaborate": "Expected number as prop, got string.", - "explain": "Expected number as prop, got string.", + "elaborate": "Expected array index as prop, got string.", + "explain": "Expected array index as prop, got string.", "left": Array [ 2, ], @@ -8339,8 +8339,8 @@ Object { exports[`Member expression type combinations: Invalid type combinations return TypeError 56`] = ` Object { - "elaborate": "Expected number as prop, got function.", - "explain": "Expected number as prop, got function.", + "elaborate": "Expected array index as prop, got function.", + "explain": "Expected array index as prop, got function.", "left": Array [ 2, ], @@ -8350,8 +8350,8 @@ Object { exports[`Member expression type combinations: Invalid type combinations return TypeError 57`] = ` Object { - "elaborate": "Expected number as prop, got function.", - "explain": "Expected number as prop, got function.", + "elaborate": "Expected array index as prop, got function.", + "explain": "Expected array index as prop, got function.", "left": Array [ 2, ], @@ -8361,8 +8361,8 @@ Object { exports[`Member expression type combinations: Invalid type combinations return TypeError 58`] = ` Object { - "elaborate": "Expected number as prop, got object.", - "explain": "Expected number as prop, got object.", + "elaborate": "Expected array index as prop, got object.", + "explain": "Expected array index as prop, got object.", "left": Array [ 2, ], @@ -8374,8 +8374,8 @@ Object { exports[`Member expression type combinations: Invalid type combinations return TypeError 59`] = ` Object { - "elaborate": "Expected number as prop, got array.", - "explain": "Expected number as prop, got array.", + "elaborate": "Expected array index as prop, got array.", + "explain": "Expected array index as prop, got array.", "left": Array [ 2, ], @@ -8387,8 +8387,8 @@ Object { exports[`Member expression type combinations: Invalid type combinations return TypeError 60`] = ` Object { - "elaborate": "Expected number as prop, got undefined.", - "explain": "Expected number as prop, got undefined.", + "elaborate": "Expected array index as prop, got undefined.", + "explain": "Expected array index as prop, got undefined.", "left": Array [ 2, ], @@ -8398,8 +8398,8 @@ Object { exports[`Member expression type combinations: Invalid type combinations return TypeError 61`] = ` Object { - "elaborate": "Expected number as prop, got null.", - "explain": "Expected number as prop, got null.", + "elaborate": "Expected array index as prop, got null.", + "explain": "Expected array index as prop, got null.", "left": Array [ 2, ], @@ -8577,6 +8577,39 @@ Object { } `; +exports[`Member expression type combinations: Invalid type combinations return TypeError 80`] = ` +Object { + "elaborate": "Expected array index as prop, got other number.", + "explain": "Expected array index as prop, got other number.", + "left": Array [ + 2, + ], + "right": -1, +} +`; + +exports[`Member expression type combinations: Invalid type combinations return TypeError 81`] = ` +Object { + "elaborate": "Expected array index as prop, got other number.", + "explain": "Expected array index as prop, got other number.", + "left": Array [ + 2, + ], + "right": 0.5, +} +`; + +exports[`Member expression type combinations: Invalid type combinations return TypeError 82`] = ` +Object { + "elaborate": "Expected array index as prop, got other number.", + "explain": "Expected array index as prop, got other number.", + "left": Array [ + 2, + ], + "right": 4294967295, +} +`; + exports[`Ternary/if test expression type combinations: Invalid type combinations return TypeError 1`] = ` Object { "elaborate": "Expected boolean as condition, got number.", diff --git a/src/utils/__tests__/rttc.ts b/src/utils/__tests__/rttc.ts index 0ec4cfc75..db8196ca6 100644 --- a/src/utils/__tests__/rttc.ts +++ b/src/utils/__tests__/rttc.ts @@ -289,6 +289,14 @@ describe('Member expression type combinations:', () => { }) }) + // Extra tests for array indices integral check. + valid.push([arr, 0]) + valid.push([arr, 10]) + valid.push([arr, 2 ** 32 - 2]) + invalid.push([arr, -1]) + invalid.push([arr, 0.5]) + invalid.push([arr, 2 ** 32 - 1]) + test('Valid type combinations are OK', () => { valid.forEach(([left, right]: [Value, Value]) => { const context = mockRuntimeContext() diff --git a/src/utils/rttc.ts b/src/utils/rttc.ts index b90735f82..7dd775edd 100644 --- a/src/utils/rttc.ts +++ b/src/utils/rttc.ts @@ -35,6 +35,10 @@ const typeOf = (v: Value) => { } const isNumber = (v: Value) => typeOf(v) === 'number' +// See section 4 of https://2ality.com/2012/12/arrays.html +// v >>> 0 === v checks that v is a valid unsigned 32-bit int +// tslint:disable-next-line:no-bitwise +const isArrayIndex = (v: Value) => isNumber(v) && v >>> 0 === v && v < 2 ** 32 - 1 const isString = (v: Value) => typeOf(v) === 'string' const isBool = (v: Value) => typeOf(v) === 'boolean' const isObject = (v: Value) => typeOf(v) === 'object' @@ -95,7 +99,11 @@ export const checkMemberAccess = (node: es.Node, obj: Value, prop: Value) => { if (isObject(obj)) { return isString(prop) ? undefined : new TypeError(node, ' as prop', 'string', typeOf(prop)) } else if (isArray(obj)) { - return isNumber(prop) ? undefined : new TypeError(node, ' as prop', 'number', typeOf(prop)) + return isArrayIndex(prop) + ? undefined + : isNumber(prop) + ? new TypeError(node, ' as prop', 'array index', 'other number') + : new TypeError(node, ' as prop', 'array index', typeOf(prop)) } else { return new TypeError(node, '', 'object or array', typeOf(obj)) }