-
Notifications
You must be signed in to change notification settings - Fork 12.9k
'in' should not operate on primitive types #41928
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
669e96b
8c7ca65
d2cd9b1
4e7ba7b
37268fc
4989346
cb430eb
afcf859
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29996,14 +29996,22 @@ 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 (!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 && ( | ||
// 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. | ||
andrewbranch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
!maybeTypeOfKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think maybeTypeOfKind is correct here, but everything else uses assignability to NonPrimitive, and this isn't any kind of base case. I'd feel happier if the whole thing used assignability. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pasting from discussion in chat:
|
||
) | ||
) { | ||
error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_not_be_a_primitive); | ||
} | ||
return booleanType; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
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(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 (6 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 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. | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
//// [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 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 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) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
=== 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 intersection1<T extends number, U extends 0 | 1 | 2>(thing: T & U) { | ||
>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, 43, 62)) | ||
} | ||
|
||
function intersection2<T>(thing: T & (0 | 1 | 2)) { | ||
>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, 47, 26)) | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.