diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 88220a3d99c5b..f4caff8f37c0b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -30310,14 +30310,36 @@ 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 to be + // + // 1. assignable to the non-primitive type, + // 2. an unconstrained type parameter, + // 3. a union or intersection including one or more type parameters, whose constituents are all assignable to the + // the non-primitive type, or are unconstrainted type parameters, or have constraints assignable to the + // non-primitive type, or + // 4. a type parameter whose constraint is + // i. an object type, + // ii. the non-primitive type, or + // iii. a union or intersection with at least one constituent assignable to an object or non-primitive type. + // + // The divergent behavior for type parameters and unions containing type parameters is a workaround for type + // parameters not being narrowable. If the right operand is a concrete type, we can error if there is any chance + // it is a primitive. But if the operand is a type parameter, it cannot be narrowed, so we don't issue an error + // unless *all* instantiations would result in an error. + // // 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 (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { - error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); + const rightTypeConstraint = getConstraintOfType(rightType); + if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || + rightTypeConstraint && ( + isTypeAssignableToKind(rightType, TypeFlags.UnionOrIntersection) && !allTypesAssignableToKind(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); } return booleanType; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 4e8d6f088d26f..8162ea6b3eff6 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1625,7 +1625,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 }, diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt new file mode 100644 index 0000000000000..90652969ed18a --- /dev/null +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.errors.txt @@ -0,0 +1,93 @@ +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(53,14): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(55,18): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(60,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. +tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts(64,12): error TS2361: The right-hand side of an 'in' expression must not be a primitive. + + +==== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts (8 errors) ==== + const validHasKey = ( + thing: T, + key: string, + ): boolean => { + return key in thing; // Ok + }; + + const alsoValidHasKey = ( + thing: T, + key: string, + ): boolean => { + 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; // Error (because all possible instantiations are errors) + ~~~~~ +!!! 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; // Ok (because narrowing is impossible) + } + + function union5(p: T | U) { + // For consistency, this should probably not be an error, because useful + // narrowing is impossible. However, this is exceptionally strange input, + // and it adds a lot of complexity to distinguish between a `T | U` where + // one constraint is non-primitive and the other is primitive and a `T | U` + // like this where both constraints have primitive and non-primitive + // constitutents. Also, the strictly sound behavior would be to error + // here, which is what's happening, so "fixing" this by suppressing the + // error seems very low-value. + "key" in p; + ~ +!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive. + if (typeof p === "object") { + "key" in p; + ~ +!!! 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 new file mode 100644 index 0000000000000..0d2488c3ad31a --- /dev/null +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.js @@ -0,0 +1,116 @@ +//// [inDoesNotOperateOnPrimitiveTypes.ts] +const validHasKey = ( + thing: T, + key: string, +): boolean => { + return key in thing; // Ok +}; + +const alsoValidHasKey = ( + thing: T, + key: string, +): boolean => { + 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; // Error (because all possible instantiations are errors) +} + +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; // Ok (because narrowing is impossible) +} + +function union5(p: T | U) { + // For consistency, this should probably not be an error, because useful + // narrowing is impossible. However, this is exceptionally strange input, + // and it adds a lot of complexity to distinguish between a `T | U` where + // one constraint is non-primitive and the other is primitive and a `T | U` + // like this where both constraints have primitive and non-primitive + // constitutents. Also, the strictly sound behavior would be to error + // here, which is what's happening, so "fixing" this by suppressing the + // error seems very low-value. + "key" in p; + if (typeof p === "object") { + "key" in p; + } +} + +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] +var validHasKey = function (thing, key) { + return key in thing; // Ok +}; +var alsoValidHasKey = function (thing, key) { + return key in thing; // Ok (as T may be instantiated with a valid type) +}; +function invalidHasKey(thing, key) { + return key in thing; // Error (because all possible instantiations are errors) +} +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; // Ok (because narrowing is impossible) +} +function union5(p) { + // For consistency, this should probably not be an error, because useful + // narrowing is impossible. However, this is exceptionally strange input, + // and it adds a lot of complexity to distinguish between a `T | U` where + // one constraint is non-primitive and the other is primitive and a `T | U` + // like this where both constraints have primitive and non-primitive + // constitutents. Also, the strictly sound behavior would be to error + // here, which is what's happening, so "fixing" this by suppressing the + // error seems very low-value. + "key" in p; + if (typeof p === "object") { + "key" in p; + } +} +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 new file mode 100644 index 0000000000000..ebeb6140a39e9 --- /dev/null +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.symbols @@ -0,0 +1,162 @@ +=== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts === +const validHasKey = ( +>validHasKey : Symbol(validHasKey, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 5)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 21)) + + thing: T, +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 39)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 21)) + + key: string, +>key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 1, 11)) + +): boolean => { + return key in thing; // Ok +>key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 1, 11)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 0, 39)) + +}; + +const alsoValidHasKey = ( +>alsoValidHasKey : Symbol(alsoValidHasKey, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 5)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 7, 25)) + + 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)) + +): boolean => { + 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)) + +}; + +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; // Error (because all possible instantiations are errors) +>key : Symbol(key, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 15, 11)) +>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; // Ok (because narrowing is impossible) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 39, 44)) +} + +function union5(p: T | U) { +>union5 : Symbol(union5, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 41, 1)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 16)) +>U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 42)) +>p : Symbol(p, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 70)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 16)) +>U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 42)) + + // For consistency, this should probably not be an error, because useful + // narrowing is impossible. However, this is exceptionally strange input, + // and it adds a lot of complexity to distinguish between a `T | U` where + // one constraint is non-primitive and the other is primitive and a `T | U` + // like this where both constraints have primitive and non-primitive + // constitutents. Also, the strictly sound behavior would be to error + // here, which is what's happening, so "fixing" this by suppressing the + // error seems very low-value. + "key" in p; +>p : Symbol(p, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 70)) + + if (typeof p === "object") { +>p : Symbol(p, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 70)) + + "key" in p; +>p : Symbol(p, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 43, 70)) + } +} + +function intersection1(thing: T & U) { +>intersection1 : Symbol(intersection1, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 56, 1)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 58, 23)) +>U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 58, 40)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 58, 62)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 58, 23)) +>U : Symbol(U, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 58, 40)) + + "key" in thing; // Error (because all possible instantiations are errors) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 58, 62)) +} + +function intersection2(thing: T & (0 | 1 | 2)) { +>intersection2 : Symbol(intersection2, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 60, 1)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 62, 23)) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 62, 26)) +>T : Symbol(T, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 62, 23)) + + "key" in thing; // Error (because all possible instantations are errors) +>thing : Symbol(thing, Decl(inDoesNotOperateOnPrimitiveTypes.ts, 62, 26)) +} + diff --git a/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types new file mode 100644 index 0000000000000..9175fd9022fc8 --- /dev/null +++ b/tests/baselines/reference/inDoesNotOperateOnPrimitiveTypes.types @@ -0,0 +1,172 @@ +=== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts === +const validHasKey = ( +>validHasKey : (thing: T, key: string) => boolean +>( thing: T, key: string,): boolean => { return key in thing; // Ok} : (thing: T, key: string) => boolean + + thing: T, +>thing : T + + key: string, +>key : string + +): boolean => { + return key in thing; // Ok +>key in thing : boolean +>key : string +>thing : T + +}; + +const alsoValidHasKey = ( +>alsoValidHasKey : (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 + + key: string, +>key : string + +): boolean => { + return key in thing; // Ok (as T may be instantiated with a valid type) +>key in thing : boolean +>key : string +>thing : T + +}; + +function invalidHasKey( +>invalidHasKey : (thing: T, key: string) => boolean + + thing: T, +>thing : T + + key: string, +>key : string + +): boolean { + return key in thing; // Error (because all possible instantiations are errors) +>key in thing : boolean +>key : string +>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; // Ok (because narrowing is impossible) +>"key" in thing : boolean +>"key" : "key" +>thing : T +} + +function union5(p: T | U) { +>union5 : (p: T | U) => void +>p : T | U + + // For consistency, this should probably not be an error, because useful + // narrowing is impossible. However, this is exceptionally strange input, + // and it adds a lot of complexity to distinguish between a `T | U` where + // one constraint is non-primitive and the other is primitive and a `T | U` + // like this where both constraints have primitive and non-primitive + // constitutents. Also, the strictly sound behavior would be to error + // here, which is what's happening, so "fixing" this by suppressing the + // error seems very low-value. + "key" in p; +>"key" in p : boolean +>"key" : "key" +>p : T | U + + if (typeof p === "object") { +>typeof p === "object" : boolean +>typeof p : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>p : T | U +>"object" : "object" + + "key" in p; +>"key" in p : boolean +>"key" : "key" +>p : T | U + } +} + +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 | 1 | 2) + + "key" in thing; // Error (because all possible instantations are errors) +>"key" in thing : boolean +>"key" : "key" +>thing : T & (0 | 1 | 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 new file mode 100644 index 0000000000000..fe3bc24a33409 --- /dev/null +++ b/tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts @@ -0,0 +1,65 @@ +const validHasKey = ( + thing: T, + key: string, +): boolean => { + return key in thing; // Ok +}; + +const alsoValidHasKey = ( + thing: T, + key: string, +): boolean => { + 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; // Error (because all possible instantiations are errors) +} + +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; // Ok (because narrowing is impossible) +} + +function union5(p: T | U) { + // For consistency, this should probably not be an error, because useful + // narrowing is impossible. However, this is exceptionally strange input, + // and it adds a lot of complexity to distinguish between a `T | U` where + // one constraint is non-primitive and the other is primitive and a `T | U` + // like this where both constraints have primitive and non-primitive + // constitutents. Also, the strictly sound behavior would be to error + // here, which is what's happening, so "fixing" this by suppressing the + // error seems very low-value. + "key" in p; + if (typeof p === "object") { + "key" in p; + } +} + +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) +}