mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
* 'in' should not operate on primitive types * accept baselines of failing tests * review * update error message * check if constraint of right type is assignable to a non primitive or instantiable non primitive * do not throw errors where narrowing is impossible * accept baselines * fix test case failures * Add more accurate comment discussion and document failing edge case in test * Update baselines Co-authored-by: Jonas Hübotter <jonas.huebotter@gmail.com>
This commit is contained in:
co-authored by
Jonas Hübotter
parent
c456bbd466
commit
b0f5e35777
+25
-3
@@ -30305,14 +30305,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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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 = <T extends object>(
|
||||
thing: T,
|
||||
key: string,
|
||||
): boolean => {
|
||||
return key in thing; // Ok
|
||||
};
|
||||
|
||||
const alsoValidHasKey = <T>(
|
||||
thing: T,
|
||||
key: string,
|
||||
): boolean => {
|
||||
return key in thing; // Ok (as T may be instantiated with a valid type)
|
||||
};
|
||||
|
||||
function invalidHasKey<T extends string | number>(
|
||||
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<T extends string | number, U extends boolean>(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<T extends object, U extends string | number>(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<T>(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<T extends object | "hello">(thing: T) {
|
||||
"key" in thing; // Ok (because narrowing is impossible)
|
||||
}
|
||||
|
||||
function union5<T extends object | string, U extends object | number>(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<T extends number, U extends 0 | 1 | 2>(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<T>(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.
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
//// [inDoesNotOperateOnPrimitiveTypes.ts]
|
||||
const validHasKey = <T extends object>(
|
||||
thing: T,
|
||||
key: string,
|
||||
): boolean => {
|
||||
return key in thing; // Ok
|
||||
};
|
||||
|
||||
const alsoValidHasKey = <T>(
|
||||
thing: T,
|
||||
key: string,
|
||||
): boolean => {
|
||||
return key in thing; // Ok (as T may be instantiated with a valid type)
|
||||
};
|
||||
|
||||
function invalidHasKey<T extends string | number>(
|
||||
thing: T,
|
||||
key: string,
|
||||
): boolean {
|
||||
return key in thing; // Error (because all possible instantiations are errors)
|
||||
}
|
||||
|
||||
function union1<T extends string | number, U extends boolean>(thing: T | U) {
|
||||
"key" in thing; // Error (because all possible instantiations are errors)
|
||||
}
|
||||
|
||||
function union2<T extends object, U extends string | number>(thing: T | U) {
|
||||
"key" in thing; // Error (because narrowing is possible)
|
||||
if (typeof thing === "object") {
|
||||
"key" in thing; // Ok
|
||||
}
|
||||
}
|
||||
|
||||
function union3<T>(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<T extends object | "hello">(thing: T) {
|
||||
"key" in thing; // Ok (because narrowing is impossible)
|
||||
}
|
||||
|
||||
function union5<T extends object | string, U extends object | number>(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<T extends number, U extends 0 | 1 | 2>(thing: T & U) {
|
||||
"key" in thing; // Error (because all possible instantiations are errors)
|
||||
}
|
||||
|
||||
function intersection2<T>(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)
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
=== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts ===
|
||||
const validHasKey = <T extends object>(
|
||||
>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 = <T>(
|
||||
>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<T extends string | number>(
|
||||
>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<T extends string | number, U extends boolean>(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<T extends object, U extends string | number>(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<T>(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<T extends object | "hello">(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<T extends object | string, U extends object | number>(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<T extends number, U extends 0 | 1 | 2>(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<T>(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))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
=== tests/cases/compiler/inDoesNotOperateOnPrimitiveTypes.ts ===
|
||||
const validHasKey = <T extends object>(
|
||||
>validHasKey : <T extends object>(thing: T, key: string) => boolean
|
||||
><T extends object>( thing: T, key: string,): boolean => { return key in thing; // Ok} : <T extends object>(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 = <T>(
|
||||
>alsoValidHasKey : <T>(thing: T, key: string) => boolean
|
||||
><T>( thing: T, key: string,): boolean => { return key in thing; // Ok (as T may be instantiated with a valid type)} : <T>(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<T extends string | number>(
|
||||
>invalidHasKey : <T extends string | number>(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<T extends string | number, U extends boolean>(thing: T | U) {
|
||||
>union1 : <T extends string | number, U extends boolean>(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<T extends object, U extends string | number>(thing: T | U) {
|
||||
>union2 : <T extends object, U extends string | number>(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<T>(thing: T | string | number) {
|
||||
>union3 : <T>(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<T extends object | "hello">(thing: T) {
|
||||
>union4 : <T extends object | "hello">(thing: T) => void
|
||||
>thing : T
|
||||
|
||||
"key" in thing; // Ok (because narrowing is impossible)
|
||||
>"key" in thing : boolean
|
||||
>"key" : "key"
|
||||
>thing : T
|
||||
}
|
||||
|
||||
function union5<T extends object | string, U extends object | number>(p: T | U) {
|
||||
>union5 : <T extends string | object, U extends number | object>(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<T extends number, U extends 0 | 1 | 2>(thing: T & U) {
|
||||
>intersection1 : <T extends number, U extends 0 | 1 | 2>(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<T>(thing: T & (0 | 1 | 2)) {
|
||||
>intersection2 : <T>(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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive.
|
||||
@@ -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.
|
||||
!!! error TS2361: The right-hand side of an 'in' expression must not be a primitive.
|
||||
@@ -0,0 +1,65 @@
|
||||
const validHasKey = <T extends object>(
|
||||
thing: T,
|
||||
key: string,
|
||||
): boolean => {
|
||||
return key in thing; // Ok
|
||||
};
|
||||
|
||||
const alsoValidHasKey = <T>(
|
||||
thing: T,
|
||||
key: string,
|
||||
): boolean => {
|
||||
return key in thing; // Ok (as T may be instantiated with a valid type)
|
||||
};
|
||||
|
||||
function invalidHasKey<T extends string | number>(
|
||||
thing: T,
|
||||
key: string,
|
||||
): boolean {
|
||||
return key in thing; // Error (because all possible instantiations are errors)
|
||||
}
|
||||
|
||||
function union1<T extends string | number, U extends boolean>(thing: T | U) {
|
||||
"key" in thing; // Error (because all possible instantiations are errors)
|
||||
}
|
||||
|
||||
function union2<T extends object, U extends string | number>(thing: T | U) {
|
||||
"key" in thing; // Error (because narrowing is possible)
|
||||
if (typeof thing === "object") {
|
||||
"key" in thing; // Ok
|
||||
}
|
||||
}
|
||||
|
||||
function union3<T>(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<T extends object | "hello">(thing: T) {
|
||||
"key" in thing; // Ok (because narrowing is impossible)
|
||||
}
|
||||
|
||||
function union5<T extends object | string, U extends object | number>(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<T extends number, U extends 0 | 1 | 2>(thing: T & U) {
|
||||
"key" in thing; // Error (because all possible instantiations are errors)
|
||||
}
|
||||
|
||||
function intersection2<T>(thing: T & (0 | 1 | 2)) {
|
||||
"key" in thing; // Error (because all possible instantations are errors)
|
||||
}
|
||||
Reference in New Issue
Block a user