From 669e96b47b0c30df959dc39fbc83754b495fa321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=BCbotter?= Date: Fri, 11 Dec 2020 10:54:27 +0100 Subject: [PATCH 1/8] 'in' should not operate on primitive types --- src/compiler/checker.ts | 2 +- ...nDoesNotOperateOnPrimitiveTypes.errors.txt | 20 ++++++++++ .../inDoesNotOperateOnPrimitiveTypes.js | 23 ++++++++++++ .../inDoesNotOperateOnPrimitiveTypes.symbols | 37 +++++++++++++++++++ .../inDoesNotOperateOnPrimitiveTypes.types | 37 +++++++++++++++++++ .../inDoesNotOperateOnPrimitiveTypes.ts | 13 +++++++ 6 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt create mode 100644 tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js create mode 100644 tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols create mode 100644 tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types create mode 100644 tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 70969b0846bf2..96d8d25f59034 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -30002,7 +30002,7 @@ namespace ts { isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.TypeParameter))) { error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol); } - if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { + if (!isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive)) { error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); } return booleanType; diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt new file mode 100644 index 0000000000000..93864ff34d95d --- /dev/null +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt @@ -0,0 +1,20 @@ +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(12,17): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + + +==== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts (1 errors) ==== + const validHasKey = ( + thing: A, + key: string, + ): boolean => { + return key in thing; + }; + + const invalidHasKey = ( + thing: A, + key: string, + ): boolean => { + return key in thing; + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + }; + \ No newline at end of file diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js new file mode 100644 index 0000000000000..a59f5df0fdacb --- /dev/null +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js @@ -0,0 +1,23 @@ +//// [inDoesNotOperateOnPrimitiveTypes.ts] +const validHasKey = ( + thing: A, + key: string, +): boolean => { + return key in thing; +}; + +const invalidHasKey = ( + thing: A, + key: string, +): boolean => { + return key in thing; +}; + + +//// [inDoesNotOperateOnPrimitiveTypes.js] +var validHasKey = function (thing, key) { + return key in thing; +}; +var invalidHasKey = function (thing, key) { + return key in thing; +}; diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols new file mode 100644 index 0000000000000..72f57c30f2b06 --- /dev/null +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols @@ -0,0 +1,37 @@ +=== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts === +const validHasKey = ( +>validHasKey : Symbol(validHasKey, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 5)) +>A : Symbol(A, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 21)) + + thing: A, +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 39)) +>A : Symbol(A, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 21)) + + key: string, +>key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 1, 11)) + +): boolean => { + return key in thing; +>key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 1, 11)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 39)) + +}; + +const invalidHasKey = ( +>invalidHasKey : Symbol(invalidHasKey, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 5)) +>A : Symbol(A, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 23)) + + thing: A, +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 26)) +>A : Symbol(A, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 23)) + + key: string, +>key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 8, 11)) + +): boolean => { + return key in thing; +>key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 8, 11)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 26)) + +}; + diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types new file mode 100644 index 0000000000000..432e3d5935889 --- /dev/null +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types @@ -0,0 +1,37 @@ +=== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts === +const validHasKey = ( +>validHasKey : (thing: A, key: string) => boolean +>( thing: A, key: string,): boolean => { return key in thing;} : (thing: A, key: string) => boolean + + thing: A, +>thing : A + + key: string, +>key : string + +): boolean => { + return key in thing; +>key in thing : boolean +>key : string +>thing : A + +}; + +const invalidHasKey = ( +>invalidHasKey : (thing: A, key: string) => boolean +>( thing: A, key: string,): boolean => { return key in thing;} : (thing: A, key: string) => boolean + + thing: A, +>thing : A + + key: string, +>key : string + +): boolean => { + return key in thing; +>key in thing : boolean +>key : string +>thing : A + +}; + diff --git a/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts b/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts new file mode 100644 index 0000000000000..c57dcb3f1eef1 --- /dev/null +++ b/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts @@ -0,0 +1,13 @@ +const validHasKey = ( + thing: A, + key: string, +): boolean => { + return key in thing; +}; + +const invalidHasKey = ( + thing: A, + key: string, +): boolean => { + return key in thing; +}; From 8c7ca657be0778ee1e2fea060cf53f65f4cbb5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=BCbotter?= Date: Fri, 11 Dec 2020 11:07:38 +0100 Subject: [PATCH 2/8] accept baselines of failing tests --- ...onditionalTypeDoesntSpinForever.errors.txt | 154 ++++ .../inOperatorWithValidOperands.errors.txt | 58 ++ .../keyofAndIndexedAccess.errors.txt | 667 ++++++++++++++++++ 3 files changed, 879 insertions(+) create mode 100644 tests/baselines/reference/conditionalTypeDoesntSpinForever.errors.txt create mode 100644 tests/baselines/reference/inOperatorWithValidOperands.errors.txt create mode 100644 tests/baselines/reference/keyofAndIndexedAccess.errors.txt diff --git a/tests/baselines/reference/conditionalTypeDoesntSpinForever.errors.txt b/tests/baselines/reference/conditionalTypeDoesntSpinForever.errors.txt new file mode 100644 index 0000000000000..06ee01223d89a --- /dev/null +++ b/tests/baselines/reference/conditionalTypeDoesntSpinForever.errors.txt @@ -0,0 +1,154 @@ +tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(23,15): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(36,19): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(53,21): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(53,45): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(65,17): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(78,38): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(94,21): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(94,42): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(94,84): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(94,129): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(97,71): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + + +==== tests/cases/compiler/conditionalTypeDoesntSpinForever.ts (11 errors) ==== + // A *self-contained* demonstration of the problem follows... + // Test this by running `tsc --target es6` on the command-line, rather than through another build tool such as Gulp, Webpack, etc. + + export enum PubSubRecordIsStoredInRedisAsA { + redisHash = "redisHash", + jsonEncodedRedisString = "jsonEncodedRedisString" + } + + export interface PubSubRecord> { + name: NAME; + record: RECORD; + identifier: IDENTIFIER; + storedAs: PubSubRecordIsStoredInRedisAsA; + maxMsToWaitBeforePublishing: number; + } + + type NameFieldConstructor = + SO_FAR extends {name: any} ? {} : { + name: (t?: TYPE) => BuildPubSubRecordType + } + + const buildNameFieldConstructor = (soFar: SO_FAR) => ( + "name" in soFar ? {} : { + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + name: (instance: TYPE = undefined) => + buildPubSubRecordType(Object.assign({}, soFar, {name: instance as TYPE}) as SO_FAR & {name: TYPE}) as BuildPubSubRecordType + } + ); + + type StoredAsConstructor = + SO_FAR extends {storedAs: any} ? {} : { + storedAsJsonEncodedRedisString: () => BuildPubSubRecordType; + storedRedisHash: () => BuildPubSubRecordType; + } + + const buildStoredAsConstructor = (soFar: SO_FAR) => ( + "storedAs" in soFar ? {} : { + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + storedAsJsonEncodedRedisString: () => + buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) as + BuildPubSubRecordType, + storedAsRedisHash: () => + buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) as + BuildPubSubRecordType, + } + ); + + type IdentifierFieldConstructor = + SO_FAR extends {identifier: any} ? {} : + SO_FAR extends {record: any} ? { + identifier: >(t?: TYPE) => BuildPubSubRecordType + } : {} + + const buildIdentifierFieldConstructor = (soFar: SO_FAR) => ( + "identifier" in soFar || (!("record" in soFar)) ? {} : { + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + identifier: (instance: TYPE = undefined) => + buildPubSubRecordType(Object.assign({}, soFar, {identifier: instance as TYPE}) as SO_FAR & {identifier: TYPE}) as BuildPubSubRecordType + } + ); + + type RecordFieldConstructor = + SO_FAR extends {record: any} ? {} : { + record: (t?: TYPE) => BuildPubSubRecordType + } + + const buildRecordFieldConstructor = (soFar: SO_FAR) => ( + "record" in soFar ? {} : { + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + record: (instance: TYPE = undefined) => + buildPubSubRecordType(Object.assign({}, soFar, {record: instance as TYPE}) as SO_FAR & {record: TYPE}) as BuildPubSubRecordType + } + ); + + type MaxMsToWaitBeforePublishingFieldConstructor = + SO_FAR extends {maxMsToWaitBeforePublishing: any} ? {} : { + maxMsToWaitBeforePublishing: (t: number) => BuildPubSubRecordType, + neverDelayPublishing: () => BuildPubSubRecordType, + } + + const buildMaxMsToWaitBeforePublishingFieldConstructor = (soFar: SO_FAR): MaxMsToWaitBeforePublishingFieldConstructor => ( + "maxMsToWaitBeforePublishing" in soFar ? {} : { + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + maxMsToWaitBeforePublishing: (instance: number = 0) => + buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: instance})) as BuildPubSubRecordType, + neverDelayPublishing: () => + buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) as BuildPubSubRecordType, + } + ) as MaxMsToWaitBeforePublishingFieldConstructor; + + type TypeConstructor = + SO_FAR extends {identifier: any, record: any, maxMsToWaitBeforePublishing: number, storedAs: PubSubRecordIsStoredInRedisAsA} ? { + type: SO_FAR, + fields: Set, + hasField: (fieldName: string | number | symbol) => fieldName is keyof SO_FAR + } : {} + + const buildType = (soFar: SO_FAR) => ( + "identifier" in soFar && "object" in soFar && "maxMsToWaitBeforePublishing" in soFar && "PubSubRecordIsStoredInRedisAsA" in soFar ? {} : { + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + type: soFar, + fields: () => new Set(Object.keys(soFar) as (keyof SO_FAR)[]), + hasField: (fieldName: string | number | symbol) => fieldName in soFar + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + } + ); + + type BuildPubSubRecordType = + NameFieldConstructor & + IdentifierFieldConstructor & + RecordFieldConstructor & + StoredAsConstructor & // infinite loop goes away when you comment out this line + MaxMsToWaitBeforePublishingFieldConstructor & + TypeConstructor + + const buildPubSubRecordType = (soFar: SO_FAR) => Object.assign( + {}, + buildNameFieldConstructor(soFar), + buildIdentifierFieldConstructor(soFar), + buildRecordFieldConstructor(soFar), + buildStoredAsConstructor(soFar), + buildMaxMsToWaitBeforePublishingFieldConstructor(soFar), + buildType(soFar) + ) as BuildPubSubRecordType; + const PubSubRecordType = buildPubSubRecordType({}); \ No newline at end of file diff --git a/tests/baselines/reference/inOperatorWithValidOperands.errors.txt b/tests/baselines/reference/inOperatorWithValidOperands.errors.txt new file mode 100644 index 0000000000000..826528075bbd6 --- /dev/null +++ b/tests/baselines/reference/inOperatorWithValidOperands.errors.txt @@ -0,0 +1,58 @@ +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts(26,20): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts(30,20): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts(34,20): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + + +==== tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts (3 errors) ==== + var x: any; + + // valid left operands + // the left operand is required to be of type Any, the String primitive type, or the Number primitive type + var a1: string; + var a2: number; + var a3: string | number | symbol; + var a4: any; + + var ra1 = x in x; + var ra2 = a1 in x; + var ra3 = a2 in x; + var ra4 = '' in x; + var ra5 = 0 in x; + var ra6 = a3 in x; + var ra7 = a4 in x; + + // valid right operands + // the right operand is required to be of type Any, an object type, or a type parameter type + var b1: {}; + + var rb1 = x in b1; + var rb2 = x in {}; + + function foo(t: T) { + var rb3 = x in t; + ~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + } + + function unionCase(t: T | U) { + var rb4 = x in t; + ~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + } + + function unionCase2(t: T | object) { + var rb5 = x in t; + ~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + } + + interface X { x: number } + interface Y { y: number } + + var c1: X | Y; + var c2: X; + var c3: Y; + + var rc1 = x in c1; + var rc2 = x in (c2 || c3); + \ No newline at end of file diff --git a/tests/baselines/reference/keyofAndIndexedAccess.errors.txt b/tests/baselines/reference/keyofAndIndexedAccess.errors.txt new file mode 100644 index 0000000000000..7fc37ebcd56b4 --- /dev/null +++ b/tests/baselines/reference/keyofAndIndexedAccess.errors.txt @@ -0,0 +1,667 @@ +tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts(205,24): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts(211,24): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + + +==== tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts (2 errors) ==== + class Shape { + name: string; + width: number; + height: number; + visible: boolean; + } + + class TaggedShape extends Shape { + tag: string; + } + + class Item { + name: string; + price: number; + } + + class Options { + visible: "yes" | "no"; + } + + type Dictionary = { [x: string]: T }; + type NumericallyIndexed = { [x: number]: T }; + + const enum E { A, B, C } + + type K00 = keyof any; // string + type K01 = keyof string; // "toString" | "charAt" | ... + type K02 = keyof number; // "toString" | "toFixed" | "toExponential" | ... + type K03 = keyof boolean; // "valueOf" + type K04 = keyof void; // never + type K05 = keyof undefined; // never + type K06 = keyof null; // never + type K07 = keyof never; // string | number | symbol + type K08 = keyof unknown; // never + + type K10 = keyof Shape; // "name" | "width" | "height" | "visible" + type K11 = keyof Shape[]; // "length" | "toString" | ... + type K12 = keyof Dictionary; // string + type K13 = keyof {}; // never + type K14 = keyof Object; // "constructor" | "toString" | ... + type K15 = keyof E; // "toString" | "toFixed" | "toExponential" | ... + type K16 = keyof [string, number]; // "0" | "1" | "length" | "toString" | ... + type K17 = keyof (Shape | Item); // "name" + type K18 = keyof (Shape & Item); // "name" | "width" | "height" | "visible" | "price" + type K19 = keyof NumericallyIndexed // never + + type KeyOf = keyof T; + + type K20 = KeyOf; // "name" | "width" | "height" | "visible" + type K21 = KeyOf>; // string + + type NAME = "name"; + type WIDTH_OR_HEIGHT = "width" | "height"; + + type Q10 = Shape["name"]; // string + type Q11 = Shape["width" | "height"]; // number + type Q12 = Shape["name" | "visible"]; // string | boolean + + type Q20 = Shape[NAME]; // string + type Q21 = Shape[WIDTH_OR_HEIGHT]; // number + + type Q30 = [string, number][0]; // string + type Q31 = [string, number][1]; // number + type Q32 = [string, number][number]; // string | number + type Q33 = [string, number][E.A]; // string + type Q34 = [string, number][E.B]; // number + type Q35 = [string, number]["0"]; // string + type Q36 = [string, number]["1"]; // string + + type Q40 = (Shape | Options)["visible"]; // boolean | "yes" | "no" + type Q41 = (Shape & Options)["visible"]; // true & "yes" | true & "no" | false & "yes" | false & "no" + + type Q50 = Dictionary["howdy"]; // Shape + type Q51 = Dictionary[123]; // Shape + type Q52 = Dictionary[E.B]; // Shape + + declare let cond: boolean; + + function getProperty(obj: T, key: K) { + return obj[key]; + } + + function setProperty(obj: T, key: K, value: T[K]) { + obj[key] = value; + } + + function f10(shape: Shape) { + let name = getProperty(shape, "name"); // string + let widthOrHeight = getProperty(shape, cond ? "width" : "height"); // number + let nameOrVisible = getProperty(shape, cond ? "name" : "visible"); // string | boolean + setProperty(shape, "name", "rectangle"); + setProperty(shape, cond ? "width" : "height", 10); + setProperty(shape, cond ? "name" : "visible", true); // Technically not safe + } + + function f11(a: Shape[]) { + let len = getProperty(a, "length"); // number + setProperty(a, "length", len); + } + + function f12(t: [Shape, boolean]) { + let len = getProperty(t, "length"); + let s2 = getProperty(t, "0"); // Shape + let b2 = getProperty(t, "1"); // boolean + } + + function f13(foo: any, bar: any) { + let x = getProperty(foo, "x"); // any + let y = getProperty(foo, "100"); // any + let z = getProperty(foo, bar); // any + } + + class Component { + props: PropType; + getProperty(key: K) { + return this.props[key]; + } + setProperty(key: K, value: PropType[K]) { + this.props[key] = value; + } + } + + function f20(component: Component) { + let name = component.getProperty("name"); // string + let widthOrHeight = component.getProperty(cond ? "width" : "height"); // number + let nameOrVisible = component.getProperty(cond ? "name" : "visible"); // string | boolean + component.setProperty("name", "rectangle"); + component.setProperty(cond ? "width" : "height", 10) + component.setProperty(cond ? "name" : "visible", true); // Technically not safe + } + + function pluck(array: T[], key: K) { + return array.map(x => x[key]); + } + + function f30(shapes: Shape[]) { + let names = pluck(shapes, "name"); // string[] + let widths = pluck(shapes, "width"); // number[] + let nameOrVisibles = pluck(shapes, cond ? "name" : "visible"); // (string | boolean)[] + } + + function f31(key: K) { + const shape: Shape = { name: "foo", width: 5, height: 10, visible: true }; + return shape[key]; // Shape[K] + } + + function f32(key: K) { + const shape: Shape = { name: "foo", width: 5, height: 10, visible: true }; + return shape[key]; // Shape[K] + } + + function f33(shape: S, key: K) { + let name = getProperty(shape, "name"); + let prop = getProperty(shape, key); + return prop; + } + + function f34(ts: TaggedShape) { + let tag1 = f33(ts, "tag"); + let tag2 = getProperty(ts, "tag"); + } + + class C { + public x: string; + protected y: string; + private z: string; + } + + // Indexed access expressions have always permitted access to private and protected members. + // For consistency we also permit such access in indexed access types. + function f40(c: C) { + type X = C["x"]; + type Y = C["y"]; + type Z = C["z"]; + let x: X = c["x"]; + let y: Y = c["y"]; + let z: Z = c["z"]; + } + + function f50(k: keyof T, s: string) { + const x1 = s as keyof T; + const x2 = k as string; + } + + function f51(k: K, s: string) { + const x1 = s as keyof T; + const x2 = k as string; + } + + function f52(obj: { [x: string]: boolean }, k: Exclude, s: string, n: number) { + const x1 = obj[s]; + const x2 = obj[n]; + const x3 = obj[k]; + } + + function f53>(obj: { [x: string]: boolean }, k: K, s: string, n: number) { + const x1 = obj[s]; + const x2 = obj[n]; + const x3 = obj[k]; + } + + function f54(obj: T, key: keyof T) { + for (let s in obj[key]) { + } + const b = "foo" in obj[key]; + ~~~~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + } + + function f55(obj: T, key: K) { + for (let s in obj[key]) { + } + const b = "foo" in obj[key]; + ~~~~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. + } + + function f60(source: T, target: T) { + for (let k in source) { + target[k] = source[k]; + } + } + + function f70(func: (k1: keyof (T | U), k2: keyof (T & U)) => void) { + func<{ a: any, b: any }, { a: any, c: any }>('a', 'a'); + func<{ a: any, b: any }, { a: any, c: any }>('a', 'b'); + func<{ a: any, b: any }, { a: any, c: any }>('a', 'c'); + } + + function f71(func: (x: T, y: U) => Partial) { + let x = func({ a: 1, b: "hello" }, { c: true }); + x.a; // number | undefined + x.b; // string | undefined + x.c; // boolean | undefined + } + + function f72(func: (x: T, y: U, k: K) => (T & U)[K]) { + let a = func({ a: 1, b: "hello" }, { c: true }, 'a'); // number + let b = func({ a: 1, b: "hello" }, { c: true }, 'b'); // string + let c = func({ a: 1, b: "hello" }, { c: true }, 'c'); // boolean + } + + function f73(func: (x: T, y: U, k: K) => (T & U)[K]) { + let a = func({ a: 1, b: "hello" }, { c: true }, 'a'); // number + let b = func({ a: 1, b: "hello" }, { c: true }, 'b'); // string + let c = func({ a: 1, b: "hello" }, { c: true }, 'c'); // boolean + } + + function f74(func: (x: T, y: U, k: K) => (T | U)[K]) { + let a = func({ a: 1, b: "hello" }, { a: 2, b: true }, 'a'); // number + let b = func({ a: 1, b: "hello" }, { a: 2, b: true }, 'b'); // string | boolean + } + + function f80(obj: T) { + let a1 = obj.a; // { x: any } + let a2 = obj['a']; // { x: any } + let a3 = obj['a'] as T['a']; // T["a"] + let x1 = obj.a.x; // any + let x2 = obj['a']['x']; // any + let x3 = obj['a']['x'] as T['a']['x']; // T["a"]["x"] + } + + function f81(obj: T) { + return obj['a']['x'] as T['a']['x']; + } + + function f82() { + let x1 = f81({ a: { x: "hello" } }); // string + let x2 = f81({ a: { x: 42 } }); // number + } + + function f83(obj: T, key: K) { + return obj[key]['x'] as T[K]['x']; + } + + function f84() { + let x1 = f83({ foo: { x: "hello" } }, "foo"); // string + let x2 = f83({ bar: { x: 42 } }, "bar"); // number + } + + class C1 { + x: number; + get(key: K) { + return this[key]; + } + set(key: K, value: this[K]) { + this[key] = value; + } + foo() { + let x1 = this.x; // number + let x2 = this["x"]; // number + let x3 = this.get("x"); // this["x"] + let x4 = getProperty(this, "x"); // this["x"] + this.x = 42; + this["x"] = 42; + this.set("x", 42); + setProperty(this, "x", 42); + } + } + + type S2 = { + a: string; + b: string; + }; + + function f90(x1: S2[keyof S2], x2: T[keyof S2], x3: S2[K]) { + x1 = x2; + x1 = x3; + x2 = x1; + x2 = x3; + x3 = x1; + x3 = x2; + x1.length; + x2.length; + x3.length; + } + + function f91(x: T, y: T[keyof T], z: T[K]) { + let a: {}; + a = x; + a = y; + a = z; + } + + function f92(x: T, y: T[keyof T], z: T[K]) { + let a: {} | null | undefined; + a = x; + a = y; + a = z; + } + + // Repros from #12011 + + class Base { + get(prop: K) { + return this[prop]; + } + set(prop: K, value: this[K]) { + this[prop] = value; + } + } + + class Person extends Base { + parts: number; + constructor(parts: number) { + super(); + this.set("parts", parts); + } + getParts() { + return this.get("parts") + } + } + + class OtherPerson { + parts: number; + constructor(parts: number) { + setProperty(this, "parts", parts); + } + getParts() { + return getProperty(this, "parts") + } + } + + // Modified repro from #12544 + + function path(obj: T, key1: K1): T[K1]; + function path(obj: T, key1: K1, key2: K2): T[K1][K2]; + function path(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; + function path(obj: any, ...keys: (string | number)[]): any; + function path(obj: any, ...keys: (string | number)[]): any { + let result = obj; + for (let k of keys) { + result = result[k]; + } + return result; + } + + type Thing = { + a: { x: number, y: string }, + b: boolean + }; + + + function f1(thing: Thing) { + let x1 = path(thing, 'a'); // { x: number, y: string } + let x2 = path(thing, 'a', 'y'); // string + let x3 = path(thing, 'b'); // boolean + let x4 = path(thing, ...['a', 'x']); // any + } + + // Repro from comment in #12114 + + const assignTo2 = (object: T, key1: K1, key2: K2) => + (value: T[K1][K2]) => object[key1][key2] = value; + + // Modified repro from #12573 + + declare function one(handler: (t: T) => void): T + var empty = one(() => {}) // inferred as {}, expected + + type Handlers = { [K in keyof T]: (t: T[K]) => void } + declare function on(handlerHash: Handlers): T + var hashOfEmpty1 = on({ test: () => {} }); // {} + var hashOfEmpty2 = on({ test: (x: boolean) => {} }); // { test: boolean } + + // Repro from #12624 + + interface Options1 { + data?: Data + computed?: Computed; + } + + declare class Component1 { + constructor(options: Options1); + get(key: K): (Data & Computed)[K]; + } + + let c1 = new Component1({ + data: { + hello: "" + } + }); + + c1.get("hello"); + + // Repro from #12625 + + interface Options2 { + data?: Data + computed?: Computed; + } + + declare class Component2 { + constructor(options: Options2); + get(key: K): (Data & Computed)[K]; + } + + // Repro from #12641 + + interface R { + p: number; + } + + function f(p: K) { + let a: any; + a[p].add; // any + } + + // Repro from #12651 + + type MethodDescriptor = { + name: string; + args: any[]; + returnValue: any; + } + + declare function dispatchMethod(name: M['name'], args: M['args']): M['returnValue']; + + type SomeMethodDescriptor = { + name: "someMethod"; + args: [string, number]; + returnValue: string[]; + } + + let result = dispatchMethod("someMethod", ["hello", 35]); + + // Repro from #13073 + + type KeyTypes = "a" | "b" + let MyThingy: { [key in KeyTypes]: string[] }; + + function addToMyThingy(key: S) { + MyThingy[key].push("a"); + } + + // Repro from #13102 + + type Handler = { + onChange: (name: keyof T) => void; + }; + + function onChangeGenericFunction(handler: Handler) { + handler.onChange('preset') + } + + // Repro from #13285 + + function updateIds, K extends string>( + obj: T, + idFields: K[], + idMapping: Partial> + ): Record { + for (const idField of idFields) { + const newId: T[K] | undefined = idMapping[obj[idField]]; + if (newId) { + obj[idField] = newId; + } + } + return obj; + } + + // Repro from #13285 + + function updateIds2( + obj: T, + key: K, + stringMap: { [oldId: string]: string } + ) { + var x = obj[key]; + stringMap[x]; // Should be OK. + } + + // Repro from #13514 + + declare function head>(list: T): T[0]; + + // Repro from #13604 + + class A { + props: T & { foo: string }; + } + + class B extends A<{ x: number}> { + f(p: this["props"]) { + p.x; + } + } + + // Repro from #13749 + + class Form { + private childFormFactories: {[K in keyof T]: (v: T[K]) => Form} + + public set(prop: K, value: T[K]) { + this.childFormFactories[prop](value) + } + } + + // Repro from #13787 + + class SampleClass

{ + public props: Readonly

{ - public props: Readonly

; - constructor(props: P) { - this.props = Object.freeze(props); - } - } - - interface Foo { - foo: string; - } - - declare function merge(obj1: T, obj2: U): T & U; - - class AnotherSampleClass extends SampleClass { - constructor(props: T) { - const foo: Foo = { foo: "bar" }; - super(merge(props, foo)); - } - - public brokenMethod() { - this.props.foo.concat; - } - } - new AnotherSampleClass({}); - - // Positive repro from #17166 - function f3>(t: T, k: K, tk: T[K]): void { - for (let key in t) { - key = k // ok, K ==> keyof T - t[key] = tk; // ok, T[K] ==> T[keyof T] - } - } - - // # 21185 - type Predicates = { - [T in keyof TaggedRecord]: (variant: TaggedRecord[keyof TaggedRecord]) => variant is TaggedRecord[T] - } - - // Repros from #23592 - - type Example = { [K in keyof T]: T[K]["prop"] }; - type Result = Example<{ a: { prop: string }; b: { prop: number } }>; - - type Helper2 = { [K in keyof T]: Extract }; - type Example2 = { [K in keyof Helper2]: Helper2[K]["prop"] }; - type Result2 = Example2<{ 1: { prop: string }; 2: { prop: number } }>; - - // Repro from #23618 - - type DBBoolTable = { [k in K]: 0 | 1 } - enum Flag { - FLAG_1 = "flag_1", - FLAG_2 = "flag_2" - } - - type SimpleDBRecord = { staticField: number } & DBBoolTable - function getFlagsFromSimpleRecord(record: SimpleDBRecord, flags: Flag[]) { - return record[flags[0]]; - } - - type DynamicDBRecord = ({ dynamicField: number } | { dynamicField: string }) & DBBoolTable - function getFlagsFromDynamicRecord(record: DynamicDBRecord, flags: Flag[]) { - return record[flags[0]]; - } - - // Repro from #21368 - - interface I { - foo: string; - } - - declare function take(p: T): void; - - function fn(o: T, k: K) { - take<{} | null | undefined>(o[k]); - take(o[k]); - } - - // Repro from #23133 - - class Unbounded { - foo(x: T[keyof T]) { - let y: {} | undefined | null = x; - } - } - - // Repro from #23940 - - interface I7 { - x: any; - } - type Foo7 = T; - declare function f7(type: K): Foo7; - - // Repro from #21770 - - type Dict = { [key in T]: number }; - type DictDict = { [key in V]: Dict }; - - function ff1(dd: DictDict, k1: V, k2: T): number { - return dd[k1][k2]; - } - - function ff2(dd: DictDict, k1: V, k2: T): number { - const d: Dict = dd[k1]; - return d[k2]; - } - - // Repro from #26409 - - const cf1 = (t: T, k: K) => - { - const s: string = t[k]; - t.cool; - }; - - const cf2 = (t: T, k: K) => - { - const s: string = t[k]; - t.cool; - }; - \ No newline at end of file diff --git a/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts b/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts index c57dcb3f1eef1..652099485b5d8 100644 --- a/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts +++ b/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts @@ -1,13 +1,20 @@ -const validHasKey = ( - thing: A, +const validHasKey = ( + thing: T, key: string, ): boolean => { return key in thing; }; -const invalidHasKey = ( - thing: A, +const alsoValidHasKey = ( + thing: T, key: string, ): boolean => { return key in thing; }; + +function invalidHasKey( + thing: T, + key: string, +): boolean { + return key in thing; +} From 4e7ba7bee7cdd6c9ce0fe784a8e86556829bd573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=BCbotter?= Date: Tue, 22 Dec 2020 10:38:25 +0100 Subject: [PATCH 4/8] update error message --- src/compiler/checker.ts | 2 +- src/compiler/diagnosticMessages.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a90c676be0343..4fd89200560f3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -30004,7 +30004,7 @@ namespace ts { } if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || rightType.immediateBaseConstraint && allTypesAssignableToKind(rightType.immediateBaseConstraint, TypeFlags.Primitive)) { - error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); + error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_not_be_a_primitive); } return booleanType; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b859d28d8a6f9..8fbc566397ea5 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1621,7 +1621,7 @@ "category": "Error", "code": 2360 }, - "The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter.": { + "The right-hand side of an 'in' expression must not be a primitive.": { "category": "Error", "code": 2361 }, From 37268fc75bd0522ebad7a24db02293fdb20ed84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=BCbotter?= Date: Tue, 22 Dec 2020 11:18:07 +0100 Subject: [PATCH 5/8] check if constraint of right type is assignable to a non primitive or instantiable non primitive --- src/compiler/checker.ts | 3 +- ...nDoesNotOperateOnPrimitiveTypes.errors.txt | 61 +++++++++++- .../inDoesNotOperateOnPrimitiveTypes.js | 61 ++++++++++++ .../inDoesNotOperateOnPrimitiveTypes.symbols | 91 +++++++++++++++++ .../inDoesNotOperateOnPrimitiveTypes.types | 99 +++++++++++++++++++ .../baselines/reference/inOperator.errors.txt | 4 +- .../inOperatorWithInvalidOperands.errors.txt | 36 +++---- .../reference/symbolType2.errors.txt | 4 +- .../inDoesNotOperateOnPrimitiveTypes.ts | 34 +++++++ 9 files changed, 367 insertions(+), 26 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4fd89200560f3..61178e6409b8c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -30002,8 +30002,9 @@ namespace ts { isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.TypeParameter))) { error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol); } + const rightTypeConstraint = getConstraintOfType(rightType); if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || - rightType.immediateBaseConstraint && allTypesAssignableToKind(rightType.immediateBaseConstraint, TypeFlags.Primitive)) { + rightTypeConstraint && !allTypesAssignableToKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_not_be_a_primitive); } return booleanType; diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt index cf6bc0d278923..734418ccedc19 100644 --- a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt @@ -1,7 +1,14 @@ -tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(19,17): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(19,17): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(23,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(27,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(34,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(41,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(45,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(49,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(53,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -==== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts (1 errors) ==== +==== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts (8 errors) ==== const validHasKey = ( thing: T, key: string, @@ -22,6 +29,54 @@ tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(19,17): error TS2361: T ): boolean { return key in thing; ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + } + + function union1(thing: T | U) { + "key" in thing; // Error (because all possible instantiations are errors) + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + } + + function union2(thing: T | U) { + "key" in thing; // Error (because narrowing is possible) + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + if (typeof thing === "object") { + "key" in thing; // Ok + } + } + + function union3(thing: T | string | number) { + "key" in thing; // Error (because narrowing is possible) + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + if (typeof thing !== "string" && typeof thing !== "number") { + "key" in thing; // Ok, because further narrowing is impossible + } + } + + function union4(thing: T) { + "key" in thing; // Error (because union includes string literal) + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + } + + function union5(thing: T) { + "key" in thing; // Error (because union includes string) + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + } + + function intersection1(thing: T & U) { + "key" in thing; // Error (because all possible instantiations are errors) + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + } + + function intersection2(thing: T & (0 | 1 | 2)) { + "key" in thing; // Error (because all possible instantations are errors) + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. } \ No newline at end of file diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js index df4af81b3e3c3..f66f4bae7df11 100644 --- a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js @@ -19,6 +19,40 @@ function invalidHasKey( ): boolean { return key in thing; } + +function union1(thing: T | U) { + "key" in thing; // Error (because all possible instantiations are errors) +} + +function union2(thing: T | U) { + "key" in thing; // Error (because narrowing is possible) + if (typeof thing === "object") { + "key" in thing; // Ok + } +} + +function union3(thing: T | string | number) { + "key" in thing; // Error (because narrowing is possible) + if (typeof thing !== "string" && typeof thing !== "number") { + "key" in thing; // Ok, because further narrowing is impossible + } +} + +function union4(thing: T) { + "key" in thing; // Error (because union includes string literal) +} + +function union5(thing: T) { + "key" in thing; // Error (because union includes string) +} + +function intersection1(thing: T & U) { + "key" in thing; // Error (because all possible instantiations are errors) +} + +function intersection2(thing: T & (0 | 1 | 2)) { + "key" in thing; // Error (because all possible instantations are errors) +} //// [inDoesNotOperateOnPrimitiveTypes.js] @@ -31,3 +65,30 @@ var alsoValidHasKey = function (thing, key) { function invalidHasKey(thing, key) { return key in thing; } +function union1(thing) { + "key" in thing; // Error (because all possible instantiations are errors) +} +function union2(thing) { + "key" in thing; // Error (because narrowing is possible) + if (typeof thing === "object") { + "key" in thing; // Ok + } +} +function union3(thing) { + "key" in thing; // Error (because narrowing is possible) + if (typeof thing !== "string" && typeof thing !== "number") { + "key" in thing; // Ok, because further narrowing is impossible + } +} +function union4(thing) { + "key" in thing; // Error (because union includes string literal) +} +function union5(thing) { + "key" in thing; // Error (because union includes string) +} +function intersection1(thing) { + "key" in thing; // Error (because all possible instantiations are errors) +} +function intersection2(thing) { + "key" in thing; // Error (because all possible instantations are errors) +} diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols index 6a2c3ffc4372e..21e8442362002 100644 --- a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols @@ -52,3 +52,94 @@ function invalidHasKey( >thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 14, 50)) } +function union1(thing: T | U) { +>union1 : Symbol(union1, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 19, 1)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 21, 16)) +>U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 21, 42)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 21, 62)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 21, 16)) +>U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 21, 42)) + + "key" in thing; // Error (because all possible instantiations are errors) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 21, 62)) +} + +function union2(thing: T | U) { +>union2 : Symbol(union2, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 23, 1)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 25, 16)) +>U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 25, 33)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 25, 61)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 25, 16)) +>U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 25, 33)) + + "key" in thing; // Error (because narrowing is possible) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 25, 61)) + + if (typeof thing === "object") { +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 25, 61)) + + "key" in thing; // Ok +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 25, 61)) + } +} + +function union3(thing: T | string | number) { +>union3 : Symbol(union3, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 30, 1)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 32, 16)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 32, 19)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 32, 16)) + + "key" in thing; // Error (because narrowing is possible) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 32, 19)) + + if (typeof thing !== "string" && typeof thing !== "number") { +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 32, 19)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 32, 19)) + + "key" in thing; // Ok, because further narrowing is impossible +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 32, 19)) + } +} + +function union4(thing: T) { +>union4 : Symbol(union4, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 37, 1)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 39, 16)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 39, 44)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 39, 16)) + + "key" in thing; // Error (because union includes string literal) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 39, 44)) +} + +function union5(thing: T) { +>union5 : Symbol(union5, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 41, 1)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 16)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 43)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 16)) + + "key" in thing; // Error (because union includes string) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 43)) +} + +function intersection1(thing: T & U) { +>intersection1 : Symbol(intersection1, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 45, 1)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 23)) +>U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 40)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 62)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 23)) +>U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 40)) + + "key" in thing; // Error (because all possible instantiations are errors) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 62)) +} + +function intersection2(thing: T & (0 | 1 | 2)) { +>intersection2 : Symbol(intersection2, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 49, 1)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 51, 23)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 51, 26)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 51, 23)) + + "key" in thing; // Error (because all possible instantations are errors) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 51, 26)) +} + diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types index 8933eaad58cda..46d930fe53b2e 100644 --- a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types @@ -51,3 +51,102 @@ function invalidHasKey( >thing : T } +function union1(thing: T | U) { +>union1 : (thing: T | U) => void +>thing : T | U + + "key" in thing; // Error (because all possible instantiations are errors) +>"key" in thing : boolean +>"key" : "key" +>thing : T | U +} + +function union2(thing: T | U) { +>union2 : (thing: T | U) => void +>thing : T | U + + "key" in thing; // Error (because narrowing is possible) +>"key" in thing : boolean +>"key" : "key" +>thing : T | U + + if (typeof thing === "object") { +>typeof thing === "object" : boolean +>typeof thing : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>thing : T | U +>"object" : "object" + + "key" in thing; // Ok +>"key" in thing : boolean +>"key" : "key" +>thing : T + } +} + +function union3(thing: T | string | number) { +>union3 : (thing: T | string | number) => void +>thing : string | number | T + + "key" in thing; // Error (because narrowing is possible) +>"key" in thing : boolean +>"key" : "key" +>thing : string | number | T + + if (typeof thing !== "string" && typeof thing !== "number") { +>typeof thing !== "string" && typeof thing !== "number" : boolean +>typeof thing !== "string" : boolean +>typeof thing : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>thing : string | number | T +>"string" : "string" +>typeof thing !== "number" : boolean +>typeof thing : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>thing : number | T +>"number" : "number" + + "key" in thing; // Ok, because further narrowing is impossible +>"key" in thing : boolean +>"key" : "key" +>thing : T + } +} + +function union4(thing: T) { +>union4 : (thing: T) => void +>thing : T + + "key" in thing; // Error (because union includes string literal) +>"key" in thing : boolean +>"key" : "key" +>thing : T +} + +function union5(thing: T) { +>union5 : (thing: T) => void +>thing : T + + "key" in thing; // Error (because union includes string) +>"key" in thing : boolean +>"key" : "key" +>thing : T +} + +function intersection1(thing: T & U) { +>intersection1 : (thing: T & U) => void +>thing : T & U + + "key" in thing; // Error (because all possible instantiations are errors) +>"key" in thing : boolean +>"key" : "key" +>thing : T & U +} + +function intersection2(thing: T & (0 | 1 | 2)) { +>intersection2 : (thing: T & (0 | 1 | 2)) => void +>thing : (T & 0) | (T & 1) | (T & 2) + + "key" in thing; // Error (because all possible instantations are errors) +>"key" in thing : boolean +>"key" : "key" +>thing : (T & 0) | (T & 1) | (T & 2) +} + diff --git a/tests/baselines/reference/inOperator.errors.txt b/tests/baselines/reference/inOperator.errors.txt index 3ab9f03595e95..a3f09a23643d6 100644 --- a/tests/baselines/reference/inOperator.errors.txt +++ b/tests/baselines/reference/inOperator.errors.txt @@ -1,4 +1,4 @@ -tests/cases/compiler/inOperator.ts(7,15): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/inOperator.ts(7,15): error TS2361: The right-hand side of an 'in' expression must not be a primitive. ==== tests/cases/compiler/inOperator.ts (1 errors) ==== @@ -10,7 +10,7 @@ tests/cases/compiler/inOperator.ts(7,15): error TS2361: The right-hand side of a var b = '' in 0; ~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. var c: any; var y: number; diff --git a/tests/baselines/reference/inOperatorWithInvalidOperands.errors.txt b/tests/baselines/reference/inOperatorWithInvalidOperands.errors.txt index 7827dc44b7647..f624cb1ff0f52 100644 --- a/tests/baselines/reference/inOperatorWithInvalidOperands.errors.txt +++ b/tests/baselines/reference/inOperatorWithInvalidOperands.errors.txt @@ -7,18 +7,18 @@ tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInv tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(23,11): error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(24,12): error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(25,12): error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(35,16): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(36,16): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(37,16): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(38,16): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(39,16): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(40,16): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(41,16): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(42,16): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(35,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(36,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(37,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(38,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(39,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(40,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(41,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(42,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(43,16): error TS2531: Object is possibly 'null'. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(44,17): error TS2532: Object is possibly 'undefined'. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(47,11): error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(47,17): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(47,17): error TS2361: The right-hand side of an 'in' expression must not be a primitive. ==== tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts (21 errors) ==== @@ -76,28 +76,28 @@ tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInv var rb1 = x in b1; ~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. var rb2 = x in b2; ~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. var rb3 = x in b3; ~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. var rb4 = x in b4; ~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. var rb5 = x in b5; ~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. var rb6 = x in 0; ~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. var rb7 = x in false; ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. var rb8 = x in ''; ~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. var rb9 = x in null; ~~~~ !!! error TS2531: Object is possibly 'null'. @@ -110,4 +110,4 @@ tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInv ~~ !!! error TS2360: The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'. ~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. \ No newline at end of file +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. \ No newline at end of file diff --git a/tests/baselines/reference/symbolType2.errors.txt b/tests/baselines/reference/symbolType2.errors.txt index 7ca8a6e7519a5..4c63d850b7b56 100644 --- a/tests/baselines/reference/symbolType2.errors.txt +++ b/tests/baselines/reference/symbolType2.errors.txt @@ -1,8 +1,8 @@ -tests/cases/conformance/es6/Symbols/symbolType2.ts(2,7): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/conformance/es6/Symbols/symbolType2.ts(2,7): error TS2361: The right-hand side of an 'in' expression must not be a primitive. ==== tests/cases/conformance/es6/Symbols/symbolType2.ts (1 errors) ==== Symbol.isConcatSpreadable in {}; "" in Symbol.toPrimitive; ~~~~~~~~~~~~~~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. \ No newline at end of file +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. \ No newline at end of file diff --git a/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts b/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts index 652099485b5d8..ceb8172245294 100644 --- a/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts +++ b/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts @@ -18,3 +18,37 @@ function invalidHasKey( ): boolean { return key in thing; } + +function union1(thing: T | U) { + "key" in thing; // Error (because all possible instantiations are errors) +} + +function union2(thing: T | U) { + "key" in thing; // Error (because narrowing is possible) + if (typeof thing === "object") { + "key" in thing; // Ok + } +} + +function union3(thing: T | string | number) { + "key" in thing; // Error (because narrowing is possible) + if (typeof thing !== "string" && typeof thing !== "number") { + "key" in thing; // Ok, because further narrowing is impossible + } +} + +function union4(thing: T) { + "key" in thing; // Error (because union includes string literal) +} + +function union5(thing: T) { + "key" in thing; // Error (because union includes string) +} + +function intersection1(thing: T & U) { + "key" in thing; // Error (because all possible instantiations are errors) +} + +function intersection2(thing: T & (0 | 1 | 2)) { + "key" in thing; // Error (because all possible instantations are errors) +} From 49893460c777efb0e3776fd974bf4e6a31d4c06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=BCbotter?= Date: Mon, 28 Dec 2020 13:17:18 +0100 Subject: [PATCH 6/8] do not throw errors where narrowing is impossible --- src/compiler/checker.ts | 8 +++- ...nDoesNotOperateOnPrimitiveTypes.errors.txt | 22 +++------- .../inDoesNotOperateOnPrimitiveTypes.js | 27 +++++------- .../inDoesNotOperateOnPrimitiveTypes.symbols | 44 +++++++------------ .../inDoesNotOperateOnPrimitiveTypes.types | 24 +++------- .../inDoesNotOperateOnPrimitiveTypes.ts | 14 +++--- 6 files changed, 52 insertions(+), 87 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 61178e6409b8c..ab9a7c549f256 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -30004,7 +30004,13 @@ namespace ts { } const rightTypeConstraint = getConstraintOfType(rightType); if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || - rightTypeConstraint && !allTypesAssignableToKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { + rightTypeConstraint && ( + // If constraint is a union/intersection, ensure no types are primitive. + isTypeAssignableToKind(rightType, TypeFlags.UnionOrIntersection) && !allTypesAssignableToKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || + // Otherwise, ensure that at least one type is not a primitive. + !maybeTypeOfKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) + ) + ) { error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_not_be_a_primitive); } return booleanType; diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt index 734418ccedc19..81cc8c26cd499 100644 --- a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt @@ -2,32 +2,30 @@ tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(19,17): error TS2361: T tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(23,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(27,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(34,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(41,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(45,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(49,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(53,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -==== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts (8 errors) ==== +==== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts (6 errors) ==== const validHasKey = ( thing: T, key: string, ): boolean => { - return key in thing; + return key in thing; // Ok }; const alsoValidHasKey = ( thing: T, key: string, ): boolean => { - return key in thing; + return key in thing; // Ok (as T may be instantiated with a valid type) }; function invalidHasKey( thing: T, key: string, ): boolean { - return key in thing; + return key in thing; // Error (because all possible instantiations are errors) ~~~~~ !!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. } @@ -52,20 +50,12 @@ tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(53,12): error TS2361: T ~~~~~ !!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. if (typeof thing !== "string" && typeof thing !== "number") { - "key" in thing; // Ok, because further narrowing is impossible + "key" in thing; // Ok (because further narrowing is impossible) } } function union4(thing: T) { - "key" in thing; // Error (because union includes string literal) - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - } - - function union5(thing: T) { - "key" in thing; // Error (because union includes string) - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + "key" in thing; // Ok (because narrowing is impossible) } function intersection1(thing: T & U) { diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js index f66f4bae7df11..41b80794e8aad 100644 --- a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js @@ -3,21 +3,21 @@ const validHasKey = ( thing: T, key: string, ): boolean => { - return key in thing; + return key in thing; // Ok }; const alsoValidHasKey = ( thing: T, key: string, ): boolean => { - return key in thing; + return key in thing; // Ok (as T may be instantiated with a valid type) }; function invalidHasKey( thing: T, key: string, ): boolean { - return key in thing; + return key in thing; // Error (because all possible instantiations are errors) } function union1(thing: T | U) { @@ -34,16 +34,12 @@ function union2(thing: T | U) { function union3(thing: T | string | number) { "key" in thing; // Error (because narrowing is possible) if (typeof thing !== "string" && typeof thing !== "number") { - "key" in thing; // Ok, because further narrowing is impossible + "key" in thing; // Ok (because further narrowing is impossible) } } function union4(thing: T) { - "key" in thing; // Error (because union includes string literal) -} - -function union5(thing: T) { - "key" in thing; // Error (because union includes string) + "key" in thing; // Ok (because narrowing is impossible) } function intersection1(thing: T & U) { @@ -57,13 +53,13 @@ function intersection2(thing: T & (0 | 1 | 2)) { //// [inDoesNotOperateOnPrimitiveTypes.js] var validHasKey = function (thing, key) { - return key in thing; + return key in thing; // Ok }; var alsoValidHasKey = function (thing, key) { - return key in thing; + return key in thing; // Ok (as T may be instantiated with a valid type) }; function invalidHasKey(thing, key) { - return key in thing; + return key in thing; // Error (because all possible instantiations are errors) } function union1(thing) { "key" in thing; // Error (because all possible instantiations are errors) @@ -77,14 +73,11 @@ function union2(thing) { function union3(thing) { "key" in thing; // Error (because narrowing is possible) if (typeof thing !== "string" && typeof thing !== "number") { - "key" in thing; // Ok, because further narrowing is impossible + "key" in thing; // Ok (because further narrowing is impossible) } } function union4(thing) { - "key" in thing; // Error (because union includes string literal) -} -function union5(thing) { - "key" in thing; // Error (because union includes string) + "key" in thing; // Ok (because narrowing is impossible) } function intersection1(thing) { "key" in thing; // Error (because all possible instantiations are errors) diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols index 21e8442362002..0bd04eadeca31 100644 --- a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols @@ -11,7 +11,7 @@ const validHasKey = ( >key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 1, 11)) ): boolean => { - return key in thing; + return key in thing; // Ok >key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 1, 11)) >thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 39)) @@ -29,7 +29,7 @@ const alsoValidHasKey = ( >key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 8, 11)) ): boolean => { - return key in thing; + return key in thing; // Ok (as T may be instantiated with a valid type) >key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 8, 11)) >thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 28)) @@ -47,7 +47,7 @@ function invalidHasKey( >key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 15, 11)) ): boolean { - return key in thing; + return key in thing; // Error (because all possible instantiations are errors) >key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 15, 11)) >thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 14, 50)) } @@ -96,7 +96,7 @@ function union3(thing: T | string | number) { >thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 32, 19)) >thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 32, 19)) - "key" in thing; // Ok, because further narrowing is impossible + "key" in thing; // Ok (because further narrowing is impossible) >thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 32, 19)) } } @@ -107,39 +107,29 @@ function union4(thing: T) { >thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 39, 44)) >T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 39, 16)) - "key" in thing; // Error (because union includes string literal) + "key" in thing; // Ok (because narrowing is impossible) >thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 39, 44)) } -function union5(thing: T) { ->union5 : Symbol(union5, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 41, 1)) ->T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 16)) ->thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 43)) ->T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 16)) - - "key" in thing; // Error (because union includes string) ->thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 43)) -} - function intersection1(thing: T & U) { ->intersection1 : Symbol(intersection1, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 45, 1)) ->T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 23)) ->U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 40)) ->thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 62)) ->T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 23)) ->U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 40)) +>intersection1 : Symbol(intersection1, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 41, 1)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 23)) +>U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 40)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 62)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 23)) +>U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 40)) "key" in thing; // Error (because all possible instantiations are errors) ->thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 62)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 62)) } function intersection2(thing: T & (0 | 1 | 2)) { ->intersection2 : Symbol(intersection2, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 49, 1)) ->T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 51, 23)) ->thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 51, 26)) ->T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 51, 23)) +>intersection2 : Symbol(intersection2, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 45, 1)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 23)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 26)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 23)) "key" in thing; // Error (because all possible instantations are errors) ->thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 51, 26)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 47, 26)) } diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types index 46d930fe53b2e..d853094aa3b79 100644 --- a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types @@ -1,7 +1,7 @@ === tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts === const validHasKey = ( >validHasKey : (thing: T, key: string) => boolean ->( thing: T, key: string,): boolean => { return key in thing;} : (thing: T, key: string) => boolean +>( thing: T, key: string,): boolean => { return key in thing; // Ok} : (thing: T, key: string) => boolean thing: T, >thing : T @@ -10,7 +10,7 @@ const validHasKey = ( >key : string ): boolean => { - return key in thing; + return key in thing; // Ok >key in thing : boolean >key : string >thing : T @@ -19,7 +19,7 @@ const validHasKey = ( const alsoValidHasKey = ( >alsoValidHasKey : (thing: T, key: string) => boolean ->( thing: T, key: string,): boolean => { return key in thing;} : (thing: T, key: string) => boolean +>( thing: T, key: string,): boolean => { return key in thing; // Ok (as T may be instantiated with a valid type)} : (thing: T, key: string) => boolean thing: T, >thing : T @@ -28,7 +28,7 @@ const alsoValidHasKey = ( >key : string ): boolean => { - return key in thing; + return key in thing; // Ok (as T may be instantiated with a valid type) >key in thing : boolean >key : string >thing : T @@ -45,7 +45,7 @@ function invalidHasKey( >key : string ): boolean { - return key in thing; + return key in thing; // Error (because all possible instantiations are errors) >key in thing : boolean >key : string >thing : T @@ -103,7 +103,7 @@ function union3(thing: T | string | number) { >thing : number | T >"number" : "number" - "key" in thing; // Ok, because further narrowing is impossible + "key" in thing; // Ok (because further narrowing is impossible) >"key" in thing : boolean >"key" : "key" >thing : T @@ -114,17 +114,7 @@ function union4(thing: T) { >union4 : (thing: T) => void >thing : T - "key" in thing; // Error (because union includes string literal) ->"key" in thing : boolean ->"key" : "key" ->thing : T -} - -function union5(thing: T) { ->union5 : (thing: T) => void ->thing : T - - "key" in thing; // Error (because union includes string) + "key" in thing; // Ok (because narrowing is impossible) >"key" in thing : boolean >"key" : "key" >thing : T diff --git a/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts b/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts index ceb8172245294..ae826556fcbf1 100644 --- a/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts +++ b/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts @@ -2,21 +2,21 @@ const validHasKey = ( thing: T, key: string, ): boolean => { - return key in thing; + return key in thing; // Ok }; const alsoValidHasKey = ( thing: T, key: string, ): boolean => { - return key in thing; + return key in thing; // Ok (as T may be instantiated with a valid type) }; function invalidHasKey( thing: T, key: string, ): boolean { - return key in thing; + return key in thing; // Error (because all possible instantiations are errors) } function union1(thing: T | U) { @@ -33,16 +33,12 @@ function union2(thing: T | U) { function union3(thing: T | string | number) { "key" in thing; // Error (because narrowing is possible) if (typeof thing !== "string" && typeof thing !== "number") { - "key" in thing; // Ok, because further narrowing is impossible + "key" in thing; // Ok (because further narrowing is impossible) } } function union4(thing: T) { - "key" in thing; // Error (because union includes string literal) -} - -function union5(thing: T) { - "key" in thing; // Error (because union includes string) + "key" in thing; // Ok (because narrowing is impossible) } function intersection1(thing: T & U) { From cb430eb13b497f86082e118afe3ef50a72722533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=BCbotter?= Date: Mon, 28 Dec 2020 14:24:28 +0100 Subject: [PATCH 7/8] accept baselines --- ...controlFlowWithTemplateLiterals.errors.txt | 16 +++ .../reference/inKeywordTypeguard.errors.txt | 35 ++++- .../inOperatorWithValidOperands.errors.txt | 55 +++++++ ...eGuardOfFromPropNameInUnionType.errors.txt | 136 ++++++++++++++++++ .../typeGuardsAsAssertions.errors.txt | 132 +++++++++++++++++ 5 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/controlFlowWithTemplateLiterals.errors.txt create mode 100644 tests/baselines/reference/inOperatorWithValidOperands.errors.txt create mode 100644 tests/baselines/reference/typeGuardOfFromPropNameInUnionType.errors.txt create mode 100644 tests/baselines/reference/typeGuardsAsAssertions.errors.txt diff --git a/tests/baselines/reference/controlFlowWithTemplateLiterals.errors.txt b/tests/baselines/reference/controlFlowWithTemplateLiterals.errors.txt new file mode 100644 index 0000000000000..b0bb1dd95e4bd --- /dev/null +++ b/tests/baselines/reference/controlFlowWithTemplateLiterals.errors.txt @@ -0,0 +1,16 @@ +tests/cases/conformance/controlFlow/controlFlowWithTemplateLiterals.ts(7,15): error TS2361: The right-hand side of an 'in' expression must not be a primitive. + + +==== tests/cases/conformance/controlFlow/controlFlowWithTemplateLiterals.ts (1 errors) ==== + declare const envVar: string | undefined; + if (typeof envVar === `string`) { + envVar.slice(0) + } + + declare const obj: {test: string} | {} + if (`test` in obj) { + ~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + obj.test.slice(0) + } + \ No newline at end of file diff --git a/tests/baselines/reference/inKeywordTypeguard.errors.txt b/tests/baselines/reference/inKeywordTypeguard.errors.txt index bb7d823d99d43..5b30df28c28ab 100644 --- a/tests/baselines/reference/inKeywordTypeguard.errors.txt +++ b/tests/baselines/reference/inKeywordTypeguard.errors.txt @@ -1,32 +1,45 @@ +tests/cases/compiler/inKeywordTypeguard.ts(5,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(6,11): error TS2339: Property 'b' does not exist on type 'A'. tests/cases/compiler/inKeywordTypeguard.ts(8,11): error TS2339: Property 'a' does not exist on type 'B'. +tests/cases/compiler/inKeywordTypeguard.ts(13,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(14,11): error TS2339: Property 'b' does not exist on type 'A'. tests/cases/compiler/inKeywordTypeguard.ts(16,11): error TS2339: Property 'a' does not exist on type 'B'. +tests/cases/compiler/inKeywordTypeguard.ts(24,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(27,11): error TS2339: Property 'b' does not exist on type 'AWithOptionalProp | BWithOptionalProp'. Property 'b' does not exist on type 'AWithOptionalProp'. +tests/cases/compiler/inKeywordTypeguard.ts(40,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(42,11): error TS2339: Property 'b' does not exist on type 'AWithMethod'. +tests/cases/compiler/inKeywordTypeguard.ts(48,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(49,11): error TS2339: Property 'a' does not exist on type 'never'. tests/cases/compiler/inKeywordTypeguard.ts(50,11): error TS2339: Property 'b' does not exist on type 'never'. tests/cases/compiler/inKeywordTypeguard.ts(52,11): error TS2339: Property 'a' does not exist on type 'AWithMethod | BWithMethod'. Property 'a' does not exist on type 'BWithMethod'. tests/cases/compiler/inKeywordTypeguard.ts(53,11): error TS2339: Property 'b' does not exist on type 'AWithMethod | BWithMethod'. Property 'b' does not exist on type 'AWithMethod'. +tests/cases/compiler/inKeywordTypeguard.ts(61,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(62,11): error TS2339: Property 'b' does not exist on type 'A | C | D'. Property 'b' does not exist on type 'A'. tests/cases/compiler/inKeywordTypeguard.ts(64,11): error TS2339: Property 'a' does not exist on type 'B'. +tests/cases/compiler/inKeywordTypeguard.ts(71,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(72,32): error TS2339: Property 'b' does not exist on type 'A'. tests/cases/compiler/inKeywordTypeguard.ts(74,32): error TS2339: Property 'a' does not exist on type 'B'. +tests/cases/compiler/inKeywordTypeguard.ts(81,20): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(82,39): error TS2339: Property 'b' does not exist on type 'A'. tests/cases/compiler/inKeywordTypeguard.ts(84,39): error TS2339: Property 'a' does not exist on type 'B'. +tests/cases/compiler/inKeywordTypeguard.ts(92,20): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' does not exist on type 'never'. +tests/cases/compiler/inKeywordTypeguard.ts(100,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/compiler/inKeywordTypeguard.ts(107,27): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -==== tests/cases/compiler/inKeywordTypeguard.ts (17 errors) ==== +==== tests/cases/compiler/inKeywordTypeguard.ts (28 errors) ==== class A { a: string; } class B { b: string; } function negativeClassesTest(x: A | B) { if ("a" in x) { + ~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. x.b = "1"; ~ !!! error TS2339: Property 'b' does not exist on type 'A'. @@ -39,6 +52,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do function positiveClassesTest(x: A | B) { if ("a" in x) { + ~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. x.b = "1"; ~ !!! error TS2339: Property 'b' does not exist on type 'A'. @@ -54,6 +69,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do function positiveTestClassesWithOptionalProperties(x: AWithOptionalProp | BWithOptionalProp) { if ("a" in x) { + ~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. x.a = "1"; } else { x.b = "1"; @@ -73,6 +90,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do function negativeTestClassesWithMembers(x: AWithMethod | BWithMethod) { if ("a" in x) { + ~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. x.a(); x.b(); ~ @@ -83,6 +102,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do function negativeTestClassesWithMemberMissingInBothClasses(x: AWithMethod | BWithMethod) { if ("c" in x) { + ~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. x.a(); ~ !!! error TS2339: Property 'a' does not exist on type 'never'. @@ -106,6 +127,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do function negativeMultipleClassesTest(x: A | B | C | D) { if ("a" in x) { + ~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. x.b = "1"; ~ !!! error TS2339: Property 'b' does not exist on type 'A | C | D'. @@ -121,6 +144,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do function negativePropTest(x: ClassWithUnionProp) { if ("a" in x.prop) { + ~~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. let y: string = x.prop.b; ~ !!! error TS2339: Property 'b' does not exist on type 'A'. @@ -135,6 +160,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do protected prop: A | B; inThis() { if ("a" in this.prop) { + ~~~~~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. let z: number = this.prop.b; ~ !!! error TS2339: Property 'b' does not exist on type 'A'. @@ -150,6 +177,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do a: string; inThis() { if ("a" in this) { + ~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. } else { let y = this.a; ~ @@ -160,6 +189,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do function positiveIntersectionTest(x: { a: string } & { b: string }) { if ("a" in x) { + ~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. let s: string = x.a; } else { let n: never = x; @@ -167,6 +198,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do } function negativeIntersectionTest() { if ("ontouchstart" in window) { + ~~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. window.ontouchstart } else { window.ontouchstart diff --git a/tests/baselines/reference/inOperatorWithValidOperands.errors.txt b/tests/baselines/reference/inOperatorWithValidOperands.errors.txt new file mode 100644 index 0000000000000..f390d009a191c --- /dev/null +++ b/tests/baselines/reference/inOperatorWithValidOperands.errors.txt @@ -0,0 +1,55 @@ +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts(44,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts(45,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. + + +==== tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts (2 errors) ==== + var x: any; + + // valid left operands + // the left operand is required to be of type Any, the String primitive type, or the Number primitive type + var a1: string; + var a2: number; + var a3: string | number | symbol; + var a4: any; + + var ra1 = x in x; + var ra2 = a1 in x; + var ra3 = a2 in x; + var ra4 = '' in x; + var ra5 = 0 in x; + var ra6 = a3 in x; + var ra7 = a4 in x; + + // valid right operands + // the right operand is required to be of type Any, an object type, or a type parameter type + var b1: {}; + + var rb1 = x in b1; + var rb2 = x in {}; + + function foo(t: T) { + var rb3 = x in t; + } + + function unionCase(t: T | U) { + var rb4 = x in t; + } + + function unionCase2(t: T | object) { + var rb5 = x in t; + } + + interface X { x: number } + interface Y { y: number } + + var c1: X | Y; + var c2: X; + var c3: Y; + + var rc1 = x in c1; + ~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + var rc2 = x in (c2 || c3); + ~~~~~~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.errors.txt b/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.errors.txt new file mode 100644 index 0000000000000..505cee8b6b47b --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.errors.txt @@ -0,0 +1,136 @@ +tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(7,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(15,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(23,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(34,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(44,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(54,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(64,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(74,20): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(86,20): error TS2361: The right-hand side of an 'in' expression must not be a primitive. + + +==== tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts (9 errors) ==== + class A { a: string; } + class B { b: number; } + class C { b: Object; } + class D { a: Date; } + + function namedClasses(x: A | B) { + if ("a" in x) { + ~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + x.a = "1"; + } else { + x.b = 1; + } + } + + function multipleClasses(x: A | B | C | D) { + if ("a" in x) { + ~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + let y: string | Date = x.a; + } else { + let z: number | Object = x.b; + } + } + + function anonymousClasses(x: { a: string; } | { b: number; }) { + if ("a" in x) { + ~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + let y: string = x.a; + } else { + let z: number = x.b; + } + } + + class AWithOptionalProp { a?: string; } + class BWithOptionalProp { b?: string; } + + function positiveTestClassesWithOptionalProperties(x: AWithOptionalProp | BWithOptionalProp) { + if ("a" in x) { + ~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + x.a = "1"; + } else { + const y: string = x instanceof AWithOptionalProp + ? x.a + : x.b + } + } + + function inParenthesizedExpression(x: A | B) { + if ("a" in (x)) { + ~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + let y: string = x.a; + } else { + let z: number = x.b; + } + } + + class ClassWithUnionProp { prop: A | B; } + + function inProperty(x: ClassWithUnionProp) { + if ("a" in x.prop) { + ~~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + let y: string = x.prop.a; + } else { + let z: number = x.prop.b; + } + } + + class NestedClassWithProp { outer: ClassWithUnionProp; } + + function innestedProperty(x: NestedClassWithProp) { + if ("a" in x.outer.prop) { + ~~~~~~~~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + let y: string = x.outer.prop.a; + } else { + let z: number = x.outer.prop.b; + } + } + + class InMemberOfClass { + protected prop: A | B; + inThis() { + if ("a" in this.prop) { + ~~~~~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + let y: string = this.prop.a; + } else { + let z: number = this.prop.b; + } + } + } + + // added for completeness + class SelfAssert { + a: string; + inThis() { + if ("a" in this) { + ~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + let y: string = this.a; + } else { + } + } + } + + interface Indexed { + [s: string]: any; + } + + function f(i: Indexed) { + if ("a" in i) { + return i.a; + } + else if ("b" in i) { + return i.b; + } + return "c" in i && i.c; + } + \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsAsAssertions.errors.txt b/tests/baselines/reference/typeGuardsAsAssertions.errors.txt new file mode 100644 index 0000000000000..87fb1e892f503 --- /dev/null +++ b/tests/baselines/reference/typeGuardsAsAssertions.errors.txt @@ -0,0 +1,132 @@ +tests/cases/conformance/controlFlow/typeGuardsAsAssertions.ts(13,22): error TS2361: The right-hand side of an 'in' expression must not be a primitive. + + +==== tests/cases/conformance/controlFlow/typeGuardsAsAssertions.ts (1 errors) ==== + // Repro from #8513 + + let cond: boolean; + + export type Optional = Some | None; + + export interface None { readonly none: string; } + export interface Some { readonly some: a; } + + export const none : None = { none: '' }; + + export function isSome(value: Optional): value is Some { + return 'some' in value; + ~~~~~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + } + + function someFrom(some: a) { + return { some }; + } + + export function fn(makeSome: () => r): void { + let result: Optional = none; + result; // None + while (cond) { + result; // Some | None + result = someFrom(isSome(result) ? result.some : makeSome()); + result; // Some + } + } + + function foo1() { + let x: string | number | boolean = 0; + x; // number + while (cond) { + x; // number, then string | number + x = typeof x === "string" ? x.slice() : "abc"; + x; // string + } + x; + } + + function foo2() { + let x: string | number | boolean = 0; + x; // number + while (cond) { + x; // number, then string | number + if (typeof x === "string") { + x = x.slice(); + } + else { + x = "abc"; + } + x; // string + } + x; + } + + // Type guards as assertions + + function f1() { + let x: string | number | undefined = undefined; + x; // undefined + if (x) { + x; // string | number (guard as assertion) + } + x; // string | number | undefined + } + + function f2() { + let x: string | number | undefined = undefined; + x; // undefined + if (typeof x === "string") { + x; // string (guard as assertion) + } + x; // string | undefined + } + + function f3() { + let x: string | number | undefined = undefined; + x; // undefined + if (!x) { + return; + } + x; // string | number (guard as assertion) + } + + function f4() { + let x: string | number | undefined = undefined; + x; // undefined + if (typeof x === "boolean") { + x; // nothing (boolean not in declared type) + } + x; // undefined + } + + function f5(x: string | number) { + if (typeof x === "string" && typeof x === "number") { + x; // number (guard as assertion) + } + else { + x; // string | number + } + x; // string | number + } + + function f6() { + let x: string | undefined | null; + x!.slice(); + x = ""; + x!.slice(); + x = undefined; + x!.slice(); + x = null; + x!.slice(); + x = undefined; + x!.slice(); + x = ""; + x!.slice(); + x = ""; + x!.slice(); + } + + function f7() { + let x: string; + x!.slice(); + } + \ No newline at end of file From afcf859e9e21ae090eff1c4fd5c13e2e571118cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=BCbotter?= Date: Mon, 28 Dec 2020 16:55:35 +0100 Subject: [PATCH 8/8] fix test case failures --- src/compiler/checker.ts | 2 +- ...controlFlowWithTemplateLiterals.errors.txt | 16 --- .../reference/inKeywordTypeguard.errors.txt | 35 +---- .../inOperatorWithValidOperands.errors.txt | 55 ------- ...eGuardOfFromPropNameInUnionType.errors.txt | 136 ------------------ .../typeGuardsAsAssertions.errors.txt | 132 ----------------- 6 files changed, 2 insertions(+), 374 deletions(-) delete mode 100644 tests/baselines/reference/controlFlowWithTemplateLiterals.errors.txt delete mode 100644 tests/baselines/reference/inOperatorWithValidOperands.errors.txt delete mode 100644 tests/baselines/reference/typeGuardOfFromPropNameInUnionType.errors.txt delete mode 100644 tests/baselines/reference/typeGuardsAsAssertions.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ab9a7c549f256..e7a64ba400e80 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -30008,7 +30008,7 @@ namespace ts { // If constraint is a union/intersection, ensure no types are primitive. isTypeAssignableToKind(rightType, TypeFlags.UnionOrIntersection) && !allTypesAssignableToKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || // Otherwise, ensure that at least one type is not a primitive. - !maybeTypeOfKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) + !maybeTypeOfKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object) ) ) { error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_not_be_a_primitive); diff --git a/tests/baselines/reference/controlFlowWithTemplateLiterals.errors.txt b/tests/baselines/reference/controlFlowWithTemplateLiterals.errors.txt deleted file mode 100644 index b0bb1dd95e4bd..0000000000000 --- a/tests/baselines/reference/controlFlowWithTemplateLiterals.errors.txt +++ /dev/null @@ -1,16 +0,0 @@ -tests/cases/conformance/controlFlow/controlFlowWithTemplateLiterals.ts(7,15): error TS2361: The right-hand side of an 'in' expression must not be a primitive. - - -==== tests/cases/conformance/controlFlow/controlFlowWithTemplateLiterals.ts (1 errors) ==== - declare const envVar: string | undefined; - if (typeof envVar === `string`) { - envVar.slice(0) - } - - declare const obj: {test: string} | {} - if (`test` in obj) { - ~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - obj.test.slice(0) - } - \ No newline at end of file diff --git a/tests/baselines/reference/inKeywordTypeguard.errors.txt b/tests/baselines/reference/inKeywordTypeguard.errors.txt index 5b30df28c28ab..bb7d823d99d43 100644 --- a/tests/baselines/reference/inKeywordTypeguard.errors.txt +++ b/tests/baselines/reference/inKeywordTypeguard.errors.txt @@ -1,45 +1,32 @@ -tests/cases/compiler/inKeywordTypeguard.ts(5,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(6,11): error TS2339: Property 'b' does not exist on type 'A'. tests/cases/compiler/inKeywordTypeguard.ts(8,11): error TS2339: Property 'a' does not exist on type 'B'. -tests/cases/compiler/inKeywordTypeguard.ts(13,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(14,11): error TS2339: Property 'b' does not exist on type 'A'. tests/cases/compiler/inKeywordTypeguard.ts(16,11): error TS2339: Property 'a' does not exist on type 'B'. -tests/cases/compiler/inKeywordTypeguard.ts(24,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(27,11): error TS2339: Property 'b' does not exist on type 'AWithOptionalProp | BWithOptionalProp'. Property 'b' does not exist on type 'AWithOptionalProp'. -tests/cases/compiler/inKeywordTypeguard.ts(40,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(42,11): error TS2339: Property 'b' does not exist on type 'AWithMethod'. -tests/cases/compiler/inKeywordTypeguard.ts(48,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(49,11): error TS2339: Property 'a' does not exist on type 'never'. tests/cases/compiler/inKeywordTypeguard.ts(50,11): error TS2339: Property 'b' does not exist on type 'never'. tests/cases/compiler/inKeywordTypeguard.ts(52,11): error TS2339: Property 'a' does not exist on type 'AWithMethod | BWithMethod'. Property 'a' does not exist on type 'BWithMethod'. tests/cases/compiler/inKeywordTypeguard.ts(53,11): error TS2339: Property 'b' does not exist on type 'AWithMethod | BWithMethod'. Property 'b' does not exist on type 'AWithMethod'. -tests/cases/compiler/inKeywordTypeguard.ts(61,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(62,11): error TS2339: Property 'b' does not exist on type 'A | C | D'. Property 'b' does not exist on type 'A'. tests/cases/compiler/inKeywordTypeguard.ts(64,11): error TS2339: Property 'a' does not exist on type 'B'. -tests/cases/compiler/inKeywordTypeguard.ts(71,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(72,32): error TS2339: Property 'b' does not exist on type 'A'. tests/cases/compiler/inKeywordTypeguard.ts(74,32): error TS2339: Property 'a' does not exist on type 'B'. -tests/cases/compiler/inKeywordTypeguard.ts(81,20): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(82,39): error TS2339: Property 'b' does not exist on type 'A'. tests/cases/compiler/inKeywordTypeguard.ts(84,39): error TS2339: Property 'a' does not exist on type 'B'. -tests/cases/compiler/inKeywordTypeguard.ts(92,20): error TS2361: The right-hand side of an 'in' expression must not be a primitive. tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' does not exist on type 'never'. -tests/cases/compiler/inKeywordTypeguard.ts(100,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -tests/cases/compiler/inKeywordTypeguard.ts(107,27): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -==== tests/cases/compiler/inKeywordTypeguard.ts (28 errors) ==== +==== tests/cases/compiler/inKeywordTypeguard.ts (17 errors) ==== class A { a: string; } class B { b: string; } function negativeClassesTest(x: A | B) { if ("a" in x) { - ~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. x.b = "1"; ~ !!! error TS2339: Property 'b' does not exist on type 'A'. @@ -52,8 +39,6 @@ tests/cases/compiler/inKeywordTypeguard.ts(107,27): error TS2361: The right-hand function positiveClassesTest(x: A | B) { if ("a" in x) { - ~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. x.b = "1"; ~ !!! error TS2339: Property 'b' does not exist on type 'A'. @@ -69,8 +54,6 @@ tests/cases/compiler/inKeywordTypeguard.ts(107,27): error TS2361: The right-hand function positiveTestClassesWithOptionalProperties(x: AWithOptionalProp | BWithOptionalProp) { if ("a" in x) { - ~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. x.a = "1"; } else { x.b = "1"; @@ -90,8 +73,6 @@ tests/cases/compiler/inKeywordTypeguard.ts(107,27): error TS2361: The right-hand function negativeTestClassesWithMembers(x: AWithMethod | BWithMethod) { if ("a" in x) { - ~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. x.a(); x.b(); ~ @@ -102,8 +83,6 @@ tests/cases/compiler/inKeywordTypeguard.ts(107,27): error TS2361: The right-hand function negativeTestClassesWithMemberMissingInBothClasses(x: AWithMethod | BWithMethod) { if ("c" in x) { - ~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. x.a(); ~ !!! error TS2339: Property 'a' does not exist on type 'never'. @@ -127,8 +106,6 @@ tests/cases/compiler/inKeywordTypeguard.ts(107,27): error TS2361: The right-hand function negativeMultipleClassesTest(x: A | B | C | D) { if ("a" in x) { - ~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. x.b = "1"; ~ !!! error TS2339: Property 'b' does not exist on type 'A | C | D'. @@ -144,8 +121,6 @@ tests/cases/compiler/inKeywordTypeguard.ts(107,27): error TS2361: The right-hand function negativePropTest(x: ClassWithUnionProp) { if ("a" in x.prop) { - ~~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. let y: string = x.prop.b; ~ !!! error TS2339: Property 'b' does not exist on type 'A'. @@ -160,8 +135,6 @@ tests/cases/compiler/inKeywordTypeguard.ts(107,27): error TS2361: The right-hand protected prop: A | B; inThis() { if ("a" in this.prop) { - ~~~~~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. let z: number = this.prop.b; ~ !!! error TS2339: Property 'b' does not exist on type 'A'. @@ -177,8 +150,6 @@ tests/cases/compiler/inKeywordTypeguard.ts(107,27): error TS2361: The right-hand a: string; inThis() { if ("a" in this) { - ~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. } else { let y = this.a; ~ @@ -189,8 +160,6 @@ tests/cases/compiler/inKeywordTypeguard.ts(107,27): error TS2361: The right-hand function positiveIntersectionTest(x: { a: string } & { b: string }) { if ("a" in x) { - ~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. let s: string = x.a; } else { let n: never = x; @@ -198,8 +167,6 @@ tests/cases/compiler/inKeywordTypeguard.ts(107,27): error TS2361: The right-hand } function negativeIntersectionTest() { if ("ontouchstart" in window) { - ~~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. window.ontouchstart } else { window.ontouchstart diff --git a/tests/baselines/reference/inOperatorWithValidOperands.errors.txt b/tests/baselines/reference/inOperatorWithValidOperands.errors.txt deleted file mode 100644 index f390d009a191c..0000000000000 --- a/tests/baselines/reference/inOperatorWithValidOperands.errors.txt +++ /dev/null @@ -1,55 +0,0 @@ -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts(44,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts(45,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. - - -==== tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts (2 errors) ==== - var x: any; - - // valid left operands - // the left operand is required to be of type Any, the String primitive type, or the Number primitive type - var a1: string; - var a2: number; - var a3: string | number | symbol; - var a4: any; - - var ra1 = x in x; - var ra2 = a1 in x; - var ra3 = a2 in x; - var ra4 = '' in x; - var ra5 = 0 in x; - var ra6 = a3 in x; - var ra7 = a4 in x; - - // valid right operands - // the right operand is required to be of type Any, an object type, or a type parameter type - var b1: {}; - - var rb1 = x in b1; - var rb2 = x in {}; - - function foo(t: T) { - var rb3 = x in t; - } - - function unionCase(t: T | U) { - var rb4 = x in t; - } - - function unionCase2(t: T | object) { - var rb5 = x in t; - } - - interface X { x: number } - interface Y { y: number } - - var c1: X | Y; - var c2: X; - var c3: Y; - - var rc1 = x in c1; - ~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - var rc2 = x in (c2 || c3); - ~~~~~~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.errors.txt b/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.errors.txt deleted file mode 100644 index 505cee8b6b47b..0000000000000 --- a/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.errors.txt +++ /dev/null @@ -1,136 +0,0 @@ -tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(7,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(15,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(23,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(34,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(44,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(54,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(64,16): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(74,20): error TS2361: The right-hand side of an 'in' expression must not be a primitive. -tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts(86,20): error TS2361: The right-hand side of an 'in' expression must not be a primitive. - - -==== tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts (9 errors) ==== - class A { a: string; } - class B { b: number; } - class C { b: Object; } - class D { a: Date; } - - function namedClasses(x: A | B) { - if ("a" in x) { - ~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - x.a = "1"; - } else { - x.b = 1; - } - } - - function multipleClasses(x: A | B | C | D) { - if ("a" in x) { - ~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - let y: string | Date = x.a; - } else { - let z: number | Object = x.b; - } - } - - function anonymousClasses(x: { a: string; } | { b: number; }) { - if ("a" in x) { - ~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - let y: string = x.a; - } else { - let z: number = x.b; - } - } - - class AWithOptionalProp { a?: string; } - class BWithOptionalProp { b?: string; } - - function positiveTestClassesWithOptionalProperties(x: AWithOptionalProp | BWithOptionalProp) { - if ("a" in x) { - ~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - x.a = "1"; - } else { - const y: string = x instanceof AWithOptionalProp - ? x.a - : x.b - } - } - - function inParenthesizedExpression(x: A | B) { - if ("a" in (x)) { - ~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - let y: string = x.a; - } else { - let z: number = x.b; - } - } - - class ClassWithUnionProp { prop: A | B; } - - function inProperty(x: ClassWithUnionProp) { - if ("a" in x.prop) { - ~~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - let y: string = x.prop.a; - } else { - let z: number = x.prop.b; - } - } - - class NestedClassWithProp { outer: ClassWithUnionProp; } - - function innestedProperty(x: NestedClassWithProp) { - if ("a" in x.outer.prop) { - ~~~~~~~~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - let y: string = x.outer.prop.a; - } else { - let z: number = x.outer.prop.b; - } - } - - class InMemberOfClass { - protected prop: A | B; - inThis() { - if ("a" in this.prop) { - ~~~~~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - let y: string = this.prop.a; - } else { - let z: number = this.prop.b; - } - } - } - - // added for completeness - class SelfAssert { - a: string; - inThis() { - if ("a" in this) { - ~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - let y: string = this.a; - } else { - } - } - } - - interface Indexed { - [s: string]: any; - } - - function f(i: Indexed) { - if ("a" in i) { - return i.a; - } - else if ("b" in i) { - return i.b; - } - return "c" in i && i.c; - } - \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsAsAssertions.errors.txt b/tests/baselines/reference/typeGuardsAsAssertions.errors.txt deleted file mode 100644 index 87fb1e892f503..0000000000000 --- a/tests/baselines/reference/typeGuardsAsAssertions.errors.txt +++ /dev/null @@ -1,132 +0,0 @@ -tests/cases/conformance/controlFlow/typeGuardsAsAssertions.ts(13,22): error TS2361: The right-hand side of an 'in' expression must not be a primitive. - - -==== tests/cases/conformance/controlFlow/typeGuardsAsAssertions.ts (1 errors) ==== - // Repro from #8513 - - let cond: boolean; - - export type Optional = Some | None; - - export interface None { readonly none: string; } - export interface Some { readonly some: a; } - - export const none : None = { none: '' }; - - export function isSome(value: Optional): value is Some { - return 'some' in value; - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. - } - - function someFrom(some: a) { - return { some }; - } - - export function fn(makeSome: () => r): void { - let result: Optional = none; - result; // None - while (cond) { - result; // Some | None - result = someFrom(isSome(result) ? result.some : makeSome()); - result; // Some - } - } - - function foo1() { - let x: string | number | boolean = 0; - x; // number - while (cond) { - x; // number, then string | number - x = typeof x === "string" ? x.slice() : "abc"; - x; // string - } - x; - } - - function foo2() { - let x: string | number | boolean = 0; - x; // number - while (cond) { - x; // number, then string | number - if (typeof x === "string") { - x = x.slice(); - } - else { - x = "abc"; - } - x; // string - } - x; - } - - // Type guards as assertions - - function f1() { - let x: string | number | undefined = undefined; - x; // undefined - if (x) { - x; // string | number (guard as assertion) - } - x; // string | number | undefined - } - - function f2() { - let x: string | number | undefined = undefined; - x; // undefined - if (typeof x === "string") { - x; // string (guard as assertion) - } - x; // string | undefined - } - - function f3() { - let x: string | number | undefined = undefined; - x; // undefined - if (!x) { - return; - } - x; // string | number (guard as assertion) - } - - function f4() { - let x: string | number | undefined = undefined; - x; // undefined - if (typeof x === "boolean") { - x; // nothing (boolean not in declared type) - } - x; // undefined - } - - function f5(x: string | number) { - if (typeof x === "string" && typeof x === "number") { - x; // number (guard as assertion) - } - else { - x; // string | number - } - x; // string | number - } - - function f6() { - let x: string | undefined | null; - x!.slice(); - x = ""; - x!.slice(); - x = undefined; - x!.slice(); - x = null; - x!.slice(); - x = undefined; - x!.slice(); - x = ""; - x!.slice(); - x = ""; - x!.slice(); - } - - function f7() { - let x: string; - x!.slice(); - } - \ No newline at end of file

; + constructor(props: P) { + this.props = Object.freeze(props); + } + } + + interface Foo { + foo: string; + } + + declare function merge(obj1: T, obj2: U): T & U; + + class AnotherSampleClass extends SampleClass { + constructor(props: T) { + const foo: Foo = { foo: "bar" }; + super(merge(props, foo)); + } + + public brokenMethod() { + this.props.foo.concat; + } + } + new AnotherSampleClass({}); + + // Positive repro from #17166 + function f3>(t: T, k: K, tk: T[K]): void { + for (let key in t) { + key = k // ok, K ==> keyof T + t[key] = tk; // ok, T[K] ==> T[keyof T] + } + } + + // # 21185 + type Predicates = { + [T in keyof TaggedRecord]: (variant: TaggedRecord[keyof TaggedRecord]) => variant is TaggedRecord[T] + } + + // Repros from #23592 + + type Example = { [K in keyof T]: T[K]["prop"] }; + type Result = Example<{ a: { prop: string }; b: { prop: number } }>; + + type Helper2 = { [K in keyof T]: Extract }; + type Example2 = { [K in keyof Helper2]: Helper2[K]["prop"] }; + type Result2 = Example2<{ 1: { prop: string }; 2: { prop: number } }>; + + // Repro from #23618 + + type DBBoolTable = { [k in K]: 0 | 1 } + enum Flag { + FLAG_1 = "flag_1", + FLAG_2 = "flag_2" + } + + type SimpleDBRecord = { staticField: number } & DBBoolTable + function getFlagsFromSimpleRecord(record: SimpleDBRecord, flags: Flag[]) { + return record[flags[0]]; + } + + type DynamicDBRecord = ({ dynamicField: number } | { dynamicField: string }) & DBBoolTable + function getFlagsFromDynamicRecord(record: DynamicDBRecord, flags: Flag[]) { + return record[flags[0]]; + } + + // Repro from #21368 + + interface I { + foo: string; + } + + declare function take(p: T): void; + + function fn(o: T, k: K) { + take<{} | null | undefined>(o[k]); + take(o[k]); + } + + // Repro from #23133 + + class Unbounded { + foo(x: T[keyof T]) { + let y: {} | undefined | null = x; + } + } + + // Repro from #23940 + + interface I7 { + x: any; + } + type Foo7 = T; + declare function f7(type: K): Foo7; + + // Repro from #21770 + + type Dict = { [key in T]: number }; + type DictDict = { [key in V]: Dict }; + + function ff1(dd: DictDict, k1: V, k2: T): number { + return dd[k1][k2]; + } + + function ff2(dd: DictDict, k1: V, k2: T): number { + const d: Dict = dd[k1]; + return d[k2]; + } + + // Repro from #26409 + + const cf1 = (t: T, k: K) => + { + const s: string = t[k]; + t.cool; + }; + + const cf2 = (t: T, k: K) => + { + const s: string = t[k]; + t.cool; + }; + \ No newline at end of file From d2cd9b1e406c3cdf10854fc16b060ab47d3fd274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=BCbotter?= Date: Wed, 16 Dec 2020 14:34:29 +0100 Subject: [PATCH 3/8] review --- src/compiler/checker.ts | 5 +- ...onditionalTypeDoesntSpinForever.errors.txt | 154 ---- ...nDoesNotOperateOnPrimitiveTypes.errors.txt | 19 +- .../inDoesNotOperateOnPrimitiveTypes.js | 20 +- .../inDoesNotOperateOnPrimitiveTypes.symbols | 39 +- .../inDoesNotOperateOnPrimitiveTypes.types | 40 +- .../inOperatorWithValidOperands.errors.txt | 58 -- .../keyofAndIndexedAccess.errors.txt | 667 ------------------ .../inDoesNotOperateOnPrimitiveTypes.ts | 15 +- 9 files changed, 98 insertions(+), 919 deletions(-) delete mode 100644 tests/baselines/reference/conditionalTypeDoesntSpinForever.errors.txt delete mode 100644 tests/baselines/reference/inOperatorWithValidOperands.errors.txt delete mode 100644 tests/baselines/reference/keyofAndIndexedAccess.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 96d8d25f59034..a90c676be0343 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29996,13 +29996,14 @@ namespace ts { rightType = checkNonNullType(rightType, right); // TypeScript 1.0 spec (April 2014): 4.15.5 // The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type, - // and the right operand to be of type Any, an object type, or a type parameter type. + // and the right operand not to extend a primitive type. // The result is always of the Boolean primitive type. if (!(allTypesAssignableToKind(leftType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) || isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.TypeParameter))) { error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol); } - if (!isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive)) { + if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || + rightType.immediateBaseConstraint && allTypesAssignableToKind(rightType.immediateBaseConstraint, TypeFlags.Primitive)) { error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); } return booleanType; diff --git a/tests/baselines/reference/conditionalTypeDoesntSpinForever.errors.txt b/tests/baselines/reference/conditionalTypeDoesntSpinForever.errors.txt deleted file mode 100644 index 06ee01223d89a..0000000000000 --- a/tests/baselines/reference/conditionalTypeDoesntSpinForever.errors.txt +++ /dev/null @@ -1,154 +0,0 @@ -tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(23,15): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(36,19): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(53,21): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(53,45): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(65,17): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(78,38): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(94,21): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(94,42): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(94,84): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(94,129): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/compiler/conditionalTypeDoesntSpinForever.ts(97,71): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - - -==== tests/cases/compiler/conditionalTypeDoesntSpinForever.ts (11 errors) ==== - // A *self-contained* demonstration of the problem follows... - // Test this by running `tsc --target es6` on the command-line, rather than through another build tool such as Gulp, Webpack, etc. - - export enum PubSubRecordIsStoredInRedisAsA { - redisHash = "redisHash", - jsonEncodedRedisString = "jsonEncodedRedisString" - } - - export interface PubSubRecord> { - name: NAME; - record: RECORD; - identifier: IDENTIFIER; - storedAs: PubSubRecordIsStoredInRedisAsA; - maxMsToWaitBeforePublishing: number; - } - - type NameFieldConstructor = - SO_FAR extends {name: any} ? {} : { - name: (t?: TYPE) => BuildPubSubRecordType - } - - const buildNameFieldConstructor = (soFar: SO_FAR) => ( - "name" in soFar ? {} : { - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - name: (instance: TYPE = undefined) => - buildPubSubRecordType(Object.assign({}, soFar, {name: instance as TYPE}) as SO_FAR & {name: TYPE}) as BuildPubSubRecordType - } - ); - - type StoredAsConstructor = - SO_FAR extends {storedAs: any} ? {} : { - storedAsJsonEncodedRedisString: () => BuildPubSubRecordType; - storedRedisHash: () => BuildPubSubRecordType; - } - - const buildStoredAsConstructor = (soFar: SO_FAR) => ( - "storedAs" in soFar ? {} : { - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - storedAsJsonEncodedRedisString: () => - buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.jsonEncodedRedisString})) as - BuildPubSubRecordType, - storedAsRedisHash: () => - buildPubSubRecordType(Object.assign({}, soFar, {storedAs: PubSubRecordIsStoredInRedisAsA.redisHash})) as - BuildPubSubRecordType, - } - ); - - type IdentifierFieldConstructor = - SO_FAR extends {identifier: any} ? {} : - SO_FAR extends {record: any} ? { - identifier: >(t?: TYPE) => BuildPubSubRecordType - } : {} - - const buildIdentifierFieldConstructor = (soFar: SO_FAR) => ( - "identifier" in soFar || (!("record" in soFar)) ? {} : { - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - identifier: (instance: TYPE = undefined) => - buildPubSubRecordType(Object.assign({}, soFar, {identifier: instance as TYPE}) as SO_FAR & {identifier: TYPE}) as BuildPubSubRecordType - } - ); - - type RecordFieldConstructor = - SO_FAR extends {record: any} ? {} : { - record: (t?: TYPE) => BuildPubSubRecordType - } - - const buildRecordFieldConstructor = (soFar: SO_FAR) => ( - "record" in soFar ? {} : { - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - record: (instance: TYPE = undefined) => - buildPubSubRecordType(Object.assign({}, soFar, {record: instance as TYPE}) as SO_FAR & {record: TYPE}) as BuildPubSubRecordType - } - ); - - type MaxMsToWaitBeforePublishingFieldConstructor = - SO_FAR extends {maxMsToWaitBeforePublishing: any} ? {} : { - maxMsToWaitBeforePublishing: (t: number) => BuildPubSubRecordType, - neverDelayPublishing: () => BuildPubSubRecordType, - } - - const buildMaxMsToWaitBeforePublishingFieldConstructor = (soFar: SO_FAR): MaxMsToWaitBeforePublishingFieldConstructor => ( - "maxMsToWaitBeforePublishing" in soFar ? {} : { - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - maxMsToWaitBeforePublishing: (instance: number = 0) => - buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: instance})) as BuildPubSubRecordType, - neverDelayPublishing: () => - buildPubSubRecordType(Object.assign({}, soFar, {maxMsToWaitBeforePublishing: 0})) as BuildPubSubRecordType, - } - ) as MaxMsToWaitBeforePublishingFieldConstructor; - - type TypeConstructor = - SO_FAR extends {identifier: any, record: any, maxMsToWaitBeforePublishing: number, storedAs: PubSubRecordIsStoredInRedisAsA} ? { - type: SO_FAR, - fields: Set, - hasField: (fieldName: string | number | symbol) => fieldName is keyof SO_FAR - } : {} - - const buildType = (soFar: SO_FAR) => ( - "identifier" in soFar && "object" in soFar && "maxMsToWaitBeforePublishing" in soFar && "PubSubRecordIsStoredInRedisAsA" in soFar ? {} : { - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - type: soFar, - fields: () => new Set(Object.keys(soFar) as (keyof SO_FAR)[]), - hasField: (fieldName: string | number | symbol) => fieldName in soFar - ~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - } - ); - - type BuildPubSubRecordType = - NameFieldConstructor & - IdentifierFieldConstructor & - RecordFieldConstructor & - StoredAsConstructor & // infinite loop goes away when you comment out this line - MaxMsToWaitBeforePublishingFieldConstructor & - TypeConstructor - - const buildPubSubRecordType = (soFar: SO_FAR) => Object.assign( - {}, - buildNameFieldConstructor(soFar), - buildIdentifierFieldConstructor(soFar), - buildRecordFieldConstructor(soFar), - buildStoredAsConstructor(soFar), - buildMaxMsToWaitBeforePublishingFieldConstructor(soFar), - buildType(soFar) - ) as BuildPubSubRecordType; - const PubSubRecordType = buildPubSubRecordType({}); \ No newline at end of file diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt index 93864ff34d95d..cf6bc0d278923 100644 --- a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt @@ -1,20 +1,27 @@ -tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(12,17): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(19,17): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. ==== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts (1 errors) ==== - const validHasKey = ( - thing: A, + const validHasKey = ( + thing: T, key: string, ): boolean => { return key in thing; }; - const invalidHasKey = ( - thing: A, + const alsoValidHasKey = ( + thing: T, key: string, ): boolean => { + return key in thing; + }; + + function invalidHasKey( + thing: T, + key: string, + ): boolean { return key in thing; ~~~~~ !!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - }; + } \ No newline at end of file diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js index a59f5df0fdacb..df4af81b3e3c3 100644 --- a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js @@ -1,23 +1,33 @@ //// [inDoesNotOperateOnPrimitiveTypes.ts] -const validHasKey = ( - thing: A, +const validHasKey = ( + thing: T, key: string, ): boolean => { return key in thing; }; -const invalidHasKey = ( - thing: A, +const alsoValidHasKey = ( + thing: T, key: string, ): boolean => { return key in thing; }; + +function invalidHasKey( + thing: T, + key: string, +): boolean { + return key in thing; +} //// [inDoesNotOperateOnPrimitiveTypes.js] var validHasKey = function (thing, key) { return key in thing; }; -var invalidHasKey = function (thing, key) { +var alsoValidHasKey = function (thing, key) { return key in thing; }; +function invalidHasKey(thing, key) { + return key in thing; +} diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols index 72f57c30f2b06..6a2c3ffc4372e 100644 --- a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols @@ -1,11 +1,11 @@ === tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts === -const validHasKey = ( +const validHasKey = ( >validHasKey : Symbol(validHasKey, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 5)) ->A : Symbol(A, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 21)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 21)) - thing: A, + thing: T, >thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 39)) ->A : Symbol(A, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 21)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 21)) key: string, >key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 1, 11)) @@ -17,13 +17,13 @@ const validHasKey = ( }; -const invalidHasKey = ( ->invalidHasKey : Symbol(invalidHasKey, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 5)) ->A : Symbol(A, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 23)) +const alsoValidHasKey = ( +>alsoValidHasKey : Symbol(alsoValidHasKey, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 5)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 25)) - thing: A, ->thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 26)) ->A : Symbol(A, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 23)) + thing: T, +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 28)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 25)) key: string, >key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 8, 11)) @@ -31,7 +31,24 @@ const invalidHasKey = ( ): boolean => { return key in thing; >key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 8, 11)) ->thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 26)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 28)) }; +function invalidHasKey( +>invalidHasKey : Symbol(invalidHasKey, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 12, 2)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 14, 23)) + + thing: T, +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 14, 50)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 14, 23)) + + key: string, +>key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 15, 11)) + +): boolean { + return key in thing; +>key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 15, 11)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 14, 50)) +} + diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types index 432e3d5935889..8933eaad58cda 100644 --- a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types @@ -1,10 +1,10 @@ === tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts === -const validHasKey = ( ->validHasKey : (thing: A, key: string) => boolean ->( thing: A, key: string,): boolean => { return key in thing;} : (thing: A, key: string) => boolean +const validHasKey = ( +>validHasKey : (thing: T, key: string) => boolean +>( thing: T, key: string,): boolean => { return key in thing;} : (thing: T, key: string) => boolean - thing: A, ->thing : A + thing: T, +>thing : T key: string, >key : string @@ -13,16 +13,16 @@ const validHasKey = ( return key in thing; >key in thing : boolean >key : string ->thing : A +>thing : T }; -const invalidHasKey = ( ->invalidHasKey : (thing: A, key: string) => boolean ->( thing: A, key: string,): boolean => { return key in thing;} : (thing: A, key: string) => boolean +const alsoValidHasKey = ( +>alsoValidHasKey : (thing: T, key: string) => boolean +>( thing: T, key: string,): boolean => { return key in thing;} : (thing: T, key: string) => boolean - thing: A, ->thing : A + thing: T, +>thing : T key: string, >key : string @@ -31,7 +31,23 @@ const invalidHasKey = ( return key in thing; >key in thing : boolean >key : string ->thing : A +>thing : T }; +function invalidHasKey( +>invalidHasKey : (thing: T, key: string) => boolean + + thing: T, +>thing : T + + key: string, +>key : string + +): boolean { + return key in thing; +>key in thing : boolean +>key : string +>thing : T +} + diff --git a/tests/baselines/reference/inOperatorWithValidOperands.errors.txt b/tests/baselines/reference/inOperatorWithValidOperands.errors.txt deleted file mode 100644 index 826528075bbd6..0000000000000 --- a/tests/baselines/reference/inOperatorWithValidOperands.errors.txt +++ /dev/null @@ -1,58 +0,0 @@ -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts(26,20): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts(30,20): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts(34,20): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - - -==== tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts (3 errors) ==== - var x: any; - - // valid left operands - // the left operand is required to be of type Any, the String primitive type, or the Number primitive type - var a1: string; - var a2: number; - var a3: string | number | symbol; - var a4: any; - - var ra1 = x in x; - var ra2 = a1 in x; - var ra3 = a2 in x; - var ra4 = '' in x; - var ra5 = 0 in x; - var ra6 = a3 in x; - var ra7 = a4 in x; - - // valid right operands - // the right operand is required to be of type Any, an object type, or a type parameter type - var b1: {}; - - var rb1 = x in b1; - var rb2 = x in {}; - - function foo(t: T) { - var rb3 = x in t; - ~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - } - - function unionCase(t: T | U) { - var rb4 = x in t; - ~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - } - - function unionCase2(t: T | object) { - var rb5 = x in t; - ~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - } - - interface X { x: number } - interface Y { y: number } - - var c1: X | Y; - var c2: X; - var c3: Y; - - var rc1 = x in c1; - var rc2 = x in (c2 || c3); - \ No newline at end of file diff --git a/tests/baselines/reference/keyofAndIndexedAccess.errors.txt b/tests/baselines/reference/keyofAndIndexedAccess.errors.txt deleted file mode 100644 index 7fc37ebcd56b4..0000000000000 --- a/tests/baselines/reference/keyofAndIndexedAccess.errors.txt +++ /dev/null @@ -1,667 +0,0 @@ -tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts(205,24): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. -tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts(211,24): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - - -==== tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts (2 errors) ==== - class Shape { - name: string; - width: number; - height: number; - visible: boolean; - } - - class TaggedShape extends Shape { - tag: string; - } - - class Item { - name: string; - price: number; - } - - class Options { - visible: "yes" | "no"; - } - - type Dictionary = { [x: string]: T }; - type NumericallyIndexed = { [x: number]: T }; - - const enum E { A, B, C } - - type K00 = keyof any; // string - type K01 = keyof string; // "toString" | "charAt" | ... - type K02 = keyof number; // "toString" | "toFixed" | "toExponential" | ... - type K03 = keyof boolean; // "valueOf" - type K04 = keyof void; // never - type K05 = keyof undefined; // never - type K06 = keyof null; // never - type K07 = keyof never; // string | number | symbol - type K08 = keyof unknown; // never - - type K10 = keyof Shape; // "name" | "width" | "height" | "visible" - type K11 = keyof Shape[]; // "length" | "toString" | ... - type K12 = keyof Dictionary; // string - type K13 = keyof {}; // never - type K14 = keyof Object; // "constructor" | "toString" | ... - type K15 = keyof E; // "toString" | "toFixed" | "toExponential" | ... - type K16 = keyof [string, number]; // "0" | "1" | "length" | "toString" | ... - type K17 = keyof (Shape | Item); // "name" - type K18 = keyof (Shape & Item); // "name" | "width" | "height" | "visible" | "price" - type K19 = keyof NumericallyIndexed // never - - type KeyOf = keyof T; - - type K20 = KeyOf; // "name" | "width" | "height" | "visible" - type K21 = KeyOf>; // string - - type NAME = "name"; - type WIDTH_OR_HEIGHT = "width" | "height"; - - type Q10 = Shape["name"]; // string - type Q11 = Shape["width" | "height"]; // number - type Q12 = Shape["name" | "visible"]; // string | boolean - - type Q20 = Shape[NAME]; // string - type Q21 = Shape[WIDTH_OR_HEIGHT]; // number - - type Q30 = [string, number][0]; // string - type Q31 = [string, number][1]; // number - type Q32 = [string, number][number]; // string | number - type Q33 = [string, number][E.A]; // string - type Q34 = [string, number][E.B]; // number - type Q35 = [string, number]["0"]; // string - type Q36 = [string, number]["1"]; // string - - type Q40 = (Shape | Options)["visible"]; // boolean | "yes" | "no" - type Q41 = (Shape & Options)["visible"]; // true & "yes" | true & "no" | false & "yes" | false & "no" - - type Q50 = Dictionary["howdy"]; // Shape - type Q51 = Dictionary[123]; // Shape - type Q52 = Dictionary[E.B]; // Shape - - declare let cond: boolean; - - function getProperty(obj: T, key: K) { - return obj[key]; - } - - function setProperty(obj: T, key: K, value: T[K]) { - obj[key] = value; - } - - function f10(shape: Shape) { - let name = getProperty(shape, "name"); // string - let widthOrHeight = getProperty(shape, cond ? "width" : "height"); // number - let nameOrVisible = getProperty(shape, cond ? "name" : "visible"); // string | boolean - setProperty(shape, "name", "rectangle"); - setProperty(shape, cond ? "width" : "height", 10); - setProperty(shape, cond ? "name" : "visible", true); // Technically not safe - } - - function f11(a: Shape[]) { - let len = getProperty(a, "length"); // number - setProperty(a, "length", len); - } - - function f12(t: [Shape, boolean]) { - let len = getProperty(t, "length"); - let s2 = getProperty(t, "0"); // Shape - let b2 = getProperty(t, "1"); // boolean - } - - function f13(foo: any, bar: any) { - let x = getProperty(foo, "x"); // any - let y = getProperty(foo, "100"); // any - let z = getProperty(foo, bar); // any - } - - class Component { - props: PropType; - getProperty(key: K) { - return this.props[key]; - } - setProperty(key: K, value: PropType[K]) { - this.props[key] = value; - } - } - - function f20(component: Component) { - let name = component.getProperty("name"); // string - let widthOrHeight = component.getProperty(cond ? "width" : "height"); // number - let nameOrVisible = component.getProperty(cond ? "name" : "visible"); // string | boolean - component.setProperty("name", "rectangle"); - component.setProperty(cond ? "width" : "height", 10) - component.setProperty(cond ? "name" : "visible", true); // Technically not safe - } - - function pluck(array: T[], key: K) { - return array.map(x => x[key]); - } - - function f30(shapes: Shape[]) { - let names = pluck(shapes, "name"); // string[] - let widths = pluck(shapes, "width"); // number[] - let nameOrVisibles = pluck(shapes, cond ? "name" : "visible"); // (string | boolean)[] - } - - function f31(key: K) { - const shape: Shape = { name: "foo", width: 5, height: 10, visible: true }; - return shape[key]; // Shape[K] - } - - function f32(key: K) { - const shape: Shape = { name: "foo", width: 5, height: 10, visible: true }; - return shape[key]; // Shape[K] - } - - function f33(shape: S, key: K) { - let name = getProperty(shape, "name"); - let prop = getProperty(shape, key); - return prop; - } - - function f34(ts: TaggedShape) { - let tag1 = f33(ts, "tag"); - let tag2 = getProperty(ts, "tag"); - } - - class C { - public x: string; - protected y: string; - private z: string; - } - - // Indexed access expressions have always permitted access to private and protected members. - // For consistency we also permit such access in indexed access types. - function f40(c: C) { - type X = C["x"]; - type Y = C["y"]; - type Z = C["z"]; - let x: X = c["x"]; - let y: Y = c["y"]; - let z: Z = c["z"]; - } - - function f50(k: keyof T, s: string) { - const x1 = s as keyof T; - const x2 = k as string; - } - - function f51(k: K, s: string) { - const x1 = s as keyof T; - const x2 = k as string; - } - - function f52(obj: { [x: string]: boolean }, k: Exclude, s: string, n: number) { - const x1 = obj[s]; - const x2 = obj[n]; - const x3 = obj[k]; - } - - function f53>(obj: { [x: string]: boolean }, k: K, s: string, n: number) { - const x1 = obj[s]; - const x2 = obj[n]; - const x3 = obj[k]; - } - - function f54(obj: T, key: keyof T) { - for (let s in obj[key]) { - } - const b = "foo" in obj[key]; - ~~~~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - } - - function f55(obj: T, key: K) { - for (let s in obj[key]) { - } - const b = "foo" in obj[key]; - ~~~~~~~~ -!!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter. - } - - function f60(source: T, target: T) { - for (let k in source) { - target[k] = source[k]; - } - } - - function f70(func: (k1: keyof (T | U), k2: keyof (T & U)) => void) { - func<{ a: any, b: any }, { a: any, c: any }>('a', 'a'); - func<{ a: any, b: any }, { a: any, c: any }>('a', 'b'); - func<{ a: any, b: any }, { a: any, c: any }>('a', 'c'); - } - - function f71(func: (x: T, y: U) => Partial) { - let x = func({ a: 1, b: "hello" }, { c: true }); - x.a; // number | undefined - x.b; // string | undefined - x.c; // boolean | undefined - } - - function f72(func: (x: T, y: U, k: K) => (T & U)[K]) { - let a = func({ a: 1, b: "hello" }, { c: true }, 'a'); // number - let b = func({ a: 1, b: "hello" }, { c: true }, 'b'); // string - let c = func({ a: 1, b: "hello" }, { c: true }, 'c'); // boolean - } - - function f73(func: (x: T, y: U, k: K) => (T & U)[K]) { - let a = func({ a: 1, b: "hello" }, { c: true }, 'a'); // number - let b = func({ a: 1, b: "hello" }, { c: true }, 'b'); // string - let c = func({ a: 1, b: "hello" }, { c: true }, 'c'); // boolean - } - - function f74(func: (x: T, y: U, k: K) => (T | U)[K]) { - let a = func({ a: 1, b: "hello" }, { a: 2, b: true }, 'a'); // number - let b = func({ a: 1, b: "hello" }, { a: 2, b: true }, 'b'); // string | boolean - } - - function f80(obj: T) { - let a1 = obj.a; // { x: any } - let a2 = obj['a']; // { x: any } - let a3 = obj['a'] as T['a']; // T["a"] - let x1 = obj.a.x; // any - let x2 = obj['a']['x']; // any - let x3 = obj['a']['x'] as T['a']['x']; // T["a"]["x"] - } - - function f81(obj: T) { - return obj['a']['x'] as T['a']['x']; - } - - function f82() { - let x1 = f81({ a: { x: "hello" } }); // string - let x2 = f81({ a: { x: 42 } }); // number - } - - function f83(obj: T, key: K) { - return obj[key]['x'] as T[K]['x']; - } - - function f84() { - let x1 = f83({ foo: { x: "hello" } }, "foo"); // string - let x2 = f83({ bar: { x: 42 } }, "bar"); // number - } - - class C1 { - x: number; - get(key: K) { - return this[key]; - } - set(key: K, value: this[K]) { - this[key] = value; - } - foo() { - let x1 = this.x; // number - let x2 = this["x"]; // number - let x3 = this.get("x"); // this["x"] - let x4 = getProperty(this, "x"); // this["x"] - this.x = 42; - this["x"] = 42; - this.set("x", 42); - setProperty(this, "x", 42); - } - } - - type S2 = { - a: string; - b: string; - }; - - function f90(x1: S2[keyof S2], x2: T[keyof S2], x3: S2[K]) { - x1 = x2; - x1 = x3; - x2 = x1; - x2 = x3; - x3 = x1; - x3 = x2; - x1.length; - x2.length; - x3.length; - } - - function f91(x: T, y: T[keyof T], z: T[K]) { - let a: {}; - a = x; - a = y; - a = z; - } - - function f92(x: T, y: T[keyof T], z: T[K]) { - let a: {} | null | undefined; - a = x; - a = y; - a = z; - } - - // Repros from #12011 - - class Base { - get(prop: K) { - return this[prop]; - } - set(prop: K, value: this[K]) { - this[prop] = value; - } - } - - class Person extends Base { - parts: number; - constructor(parts: number) { - super(); - this.set("parts", parts); - } - getParts() { - return this.get("parts") - } - } - - class OtherPerson { - parts: number; - constructor(parts: number) { - setProperty(this, "parts", parts); - } - getParts() { - return getProperty(this, "parts") - } - } - - // Modified repro from #12544 - - function path(obj: T, key1: K1): T[K1]; - function path(obj: T, key1: K1, key2: K2): T[K1][K2]; - function path(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; - function path(obj: any, ...keys: (string | number)[]): any; - function path(obj: any, ...keys: (string | number)[]): any { - let result = obj; - for (let k of keys) { - result = result[k]; - } - return result; - } - - type Thing = { - a: { x: number, y: string }, - b: boolean - }; - - - function f1(thing: Thing) { - let x1 = path(thing, 'a'); // { x: number, y: string } - let x2 = path(thing, 'a', 'y'); // string - let x3 = path(thing, 'b'); // boolean - let x4 = path(thing, ...['a', 'x']); // any - } - - // Repro from comment in #12114 - - const assignTo2 = (object: T, key1: K1, key2: K2) => - (value: T[K1][K2]) => object[key1][key2] = value; - - // Modified repro from #12573 - - declare function one(handler: (t: T) => void): T - var empty = one(() => {}) // inferred as {}, expected - - type Handlers = { [K in keyof T]: (t: T[K]) => void } - declare function on(handlerHash: Handlers): T - var hashOfEmpty1 = on({ test: () => {} }); // {} - var hashOfEmpty2 = on({ test: (x: boolean) => {} }); // { test: boolean } - - // Repro from #12624 - - interface Options1 { - data?: Data - computed?: Computed; - } - - declare class Component1 { - constructor(options: Options1); - get(key: K): (Data & Computed)[K]; - } - - let c1 = new Component1({ - data: { - hello: "" - } - }); - - c1.get("hello"); - - // Repro from #12625 - - interface Options2 { - data?: Data - computed?: Computed; - } - - declare class Component2 { - constructor(options: Options2); - get(key: K): (Data & Computed)[K]; - } - - // Repro from #12641 - - interface R { - p: number; - } - - function f(p: K) { - let a: any; - a[p].add; // any - } - - // Repro from #12651 - - type MethodDescriptor = { - name: string; - args: any[]; - returnValue: any; - } - - declare function dispatchMethod(name: M['name'], args: M['args']): M['returnValue']; - - type SomeMethodDescriptor = { - name: "someMethod"; - args: [string, number]; - returnValue: string[]; - } - - let result = dispatchMethod("someMethod", ["hello", 35]); - - // Repro from #13073 - - type KeyTypes = "a" | "b" - let MyThingy: { [key in KeyTypes]: string[] }; - - function addToMyThingy(key: S) { - MyThingy[key].push("a"); - } - - // Repro from #13102 - - type Handler = { - onChange: (name: keyof T) => void; - }; - - function onChangeGenericFunction(handler: Handler) { - handler.onChange('preset') - } - - // Repro from #13285 - - function updateIds, K extends string>( - obj: T, - idFields: K[], - idMapping: Partial> - ): Record { - for (const idField of idFields) { - const newId: T[K] | undefined = idMapping[obj[idField]]; - if (newId) { - obj[idField] = newId; - } - } - return obj; - } - - // Repro from #13285 - - function updateIds2( - obj: T, - key: K, - stringMap: { [oldId: string]: string } - ) { - var x = obj[key]; - stringMap[x]; // Should be OK. - } - - // Repro from #13514 - - declare function head>(list: T): T[0]; - - // Repro from #13604 - - class A { - props: T & { foo: string }; - } - - class B extends A<{ x: number}> { - f(p: this["props"]) { - p.x; - } - } - - // Repro from #13749 - - class Form { - private childFormFactories: {[K in keyof T]: (v: T[K]) => Form} - - public set(prop: K, value: T[K]) { - this.childFormFactories[prop](value) - } - } - - // Repro from #13787 - - class SampleClass