From 10a7e6ba70ce41038625e9039aaf580e232fd5fd Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 22 Oct 2014 16:57:43 -0700 Subject: [PATCH 1/2] Fixing in, for...in, and instanceof with union type operands --- src/compiler/checker.ts | 18 +++++++++++------- src/compiler/types.ts | 1 + .../inOperatorWithInvalidOperands.ts | 13 ++++++++----- .../inOperator/inOperatorWithValidOperands.ts | 12 +++++++++++- 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 229fcf51f17..6f10e145fd3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4143,8 +4143,8 @@ module ts { // Get the narrowed type of a given symbol at a given location function getNarrowedTypeOfSymbol(symbol: Symbol, node: Node) { var type = getTypeOfSymbol(symbol); - // Only narrow when symbol is variable of a non-primitive type - if (symbol.flags & SymbolFlags.Variable && isTypeAnyOrObjectOrTypeParameter(type)) { + // Only narrow when symbol is variable of a structured type + if (symbol.flags & SymbolFlags.Variable && type.flags & TypeFlags.Structured) { while (true) { var child = node; node = node.parent; @@ -5679,8 +5679,12 @@ module ts { return numberType; } - function isTypeAnyOrObjectOrTypeParameter(type: Type): boolean { - return (type.flags & (TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.TypeParameter)) !== 0; + // Return true if type is any, an object type, a type parameter, or a union type composed of only those kinds of types + function isStructuredType(type: Type): boolean { + if (type.flags & TypeFlags.Union) { + return !forEach((type).types, t => !isStructuredType(t)); + } + return (type.flags & TypeFlags.Structured) !== 0; } function checkInstanceOfExpression(node: BinaryExpression, leftType: Type, rightType: Type): Type { @@ -5689,7 +5693,7 @@ module ts { // and the right operand to be of type Any or a subtype of the 'Function' interface type. // The result is always of the Boolean primitive type. // NOTE: do not raise error if leftType is unknown as related error was already reported - if (leftType !== unknownType && !isTypeAnyOrObjectOrTypeParameter(leftType)) { + if (leftType !== unknownType && !isStructuredType(leftType)) { error(node.left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); } // NOTE: do not raise error if right is unknown as related error was already reported @@ -5707,7 +5711,7 @@ module ts { if (leftType !== anyType && leftType !== stringType && leftType !== numberType) { error(node.left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_types_any_string_or_number); } - if (!isTypeAnyOrObjectOrTypeParameter(rightType)) { + if (!isStructuredType(rightType)) { error(node.right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); } return booleanType; @@ -6928,7 +6932,7 @@ module ts { var exprType = checkExpression(node.expression); // unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved // in this case error about missing name is already reported - do not report extra one - if (!isTypeAnyOrObjectOrTypeParameter(exprType) && exprType !== unknownType) { + if (!isStructuredType(exprType) && exprType !== unknownType) { error(node.expression, Diagnostics.The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ea58b1166c6..48c74d8f2af 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -909,6 +909,7 @@ module ts { StringLike = String | StringLiteral, NumberLike = Number | Enum, ObjectType = Class | Interface | Reference | Tuple | Anonymous, + Structured = Any | ObjectType | Union | TypeParameter } // Properties common to all types diff --git a/tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts b/tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts index c0c93155671..0c6f1403220 100644 --- a/tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts +++ b/tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts @@ -25,16 +25,19 @@ var b1: number; var b2: boolean; var b3: string; var b4: void; +var b5: string | number; var rb1 = x in b1; var rb2 = x in b2; var rb3 = x in b3; var rb4 = x in b4; -var rb5 = x in 0; -var rb6 = x in false; -var rb7 = x in ''; -var rb8 = x in null; -var rb9 = x in undefined; +var rb5 = x in b5; +var rb6 = x in 0; +var rb7 = x in false; +var rb8 = x in ''; +var rb9 = x in null; +var rb10 = x in undefined; + // both operands are invalid var rc1 = {} in ''; \ No newline at end of file diff --git a/tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts b/tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts index 400d59fff65..a4cdfd00a6b 100644 --- a/tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts +++ b/tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithValidOperands.ts @@ -20,4 +20,14 @@ var rb2 = x in {}; function foo(t: T) { var rb3 = x in t; -} \ No newline at end of file +} + +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); From 5505371c5a3dc2e7f5b3bd21aa15b4f6e96ea537 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 22 Oct 2014 16:58:13 -0700 Subject: [PATCH 2/2] Accepting new baselines --- .../inOperatorWithInvalidOperands.errors.txt | 34 ++++++++++------- .../inOperatorWithInvalidOperands.js | 25 ++++++++----- .../reference/inOperatorWithValidOperands.js | 18 ++++++++- .../inOperatorWithValidOperands.types | 37 +++++++++++++++++++ 4 files changed, 89 insertions(+), 25 deletions(-) diff --git a/tests/baselines/reference/inOperatorWithInvalidOperands.errors.txt b/tests/baselines/reference/inOperatorWithInvalidOperands.errors.txt index 9411d016dcb..5f538eef40c 100644 --- a/tests/baselines/reference/inOperatorWithInvalidOperands.errors.txt +++ b/tests/baselines/reference/inOperatorWithInvalidOperands.errors.txt @@ -7,7 +7,6 @@ tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInv tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(18,11): error TS2360: The left-hand side of an 'in' expression must be of types 'any', 'string' or 'number'. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(19,11): error TS2360: The left-hand side of an 'in' expression must be of types 'any', 'string' or 'number'. tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(20,11): error TS2360: The left-hand side of an 'in' expression must be of types 'any', 'string' or 'number'. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(29,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(30,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(31,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(32,16): error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter @@ -16,11 +15,13 @@ tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInv 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(40,11): error TS2360: The left-hand side of an 'in' expression must be of types 'any', 'string' or 'number'. -tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(40,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(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,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(43,11): error TS2360: The left-hand side of an 'in' expression must be of types 'any', 'string' or 'number'. +tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts(43,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 (20 errors) ==== +==== tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInvalidOperands.ts (21 errors) ==== enum E { a } var x: any; @@ -66,6 +67,7 @@ tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInv var b2: boolean; var b3: string; var b4: void; + var b5: string | number; var rb1 = x in b1; ~~ @@ -79,22 +81,26 @@ tests/cases/conformance/expressions/binaryOperators/inOperator/inOperatorWithInv 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 - var rb5 = 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 - var rb6 = 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 - var rb7 = x in ''; + 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 - var rb8 = x in null; + 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 + 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 + 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 + var rb9 = x in null; ~~~~ !!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter - var rb9 = x in undefined; - ~~~~~~~~~ + var rb10 = x in undefined; + ~~~~~~~~~ !!! error TS2361: The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter + // both operands are invalid var rc1 = {} in ''; ~~ diff --git a/tests/baselines/reference/inOperatorWithInvalidOperands.js b/tests/baselines/reference/inOperatorWithInvalidOperands.js index eb117aafde3..29293f880ed 100644 --- a/tests/baselines/reference/inOperatorWithInvalidOperands.js +++ b/tests/baselines/reference/inOperatorWithInvalidOperands.js @@ -26,16 +26,19 @@ var b1: number; var b2: boolean; var b3: string; var b4: void; +var b5: string | number; var rb1 = x in b1; var rb2 = x in b2; var rb3 = x in b3; var rb4 = x in b4; -var rb5 = x in 0; -var rb6 = x in false; -var rb7 = x in ''; -var rb8 = x in null; -var rb9 = x in undefined; +var rb5 = x in b5; +var rb6 = x in 0; +var rb7 = x in false; +var rb8 = x in ''; +var rb9 = x in null; +var rb10 = x in undefined; + // both operands are invalid var rc1 = {} in ''; @@ -67,14 +70,16 @@ var b1; var b2; var b3; var b4; +var b5; var rb1 = x in b1; var rb2 = x in b2; var rb3 = x in b3; var rb4 = x in b4; -var rb5 = x in 0; -var rb6 = x in false; -var rb7 = x in ''; -var rb8 = x in null; -var rb9 = x in undefined; +var rb5 = x in b5; +var rb6 = x in 0; +var rb7 = x in false; +var rb8 = x in ''; +var rb9 = x in null; +var rb10 = x in undefined; // both operands are invalid var rc1 = {} in ''; diff --git a/tests/baselines/reference/inOperatorWithValidOperands.js b/tests/baselines/reference/inOperatorWithValidOperands.js index 16df96f22fb..561abbe340b 100644 --- a/tests/baselines/reference/inOperatorWithValidOperands.js +++ b/tests/baselines/reference/inOperatorWithValidOperands.js @@ -21,7 +21,18 @@ var rb2 = x in {}; function foo(t: T) { var rb3 = 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; +var rc2 = x in (c2 || c3); + //// [inOperatorWithValidOperands.js] var x; @@ -42,3 +53,8 @@ var rb2 = x in {}; function foo(t) { var rb3 = x in t; } +var c1; +var c2; +var c3; +var rc1 = x in c1; +var rc2 = x in (c2 || c3); diff --git a/tests/baselines/reference/inOperatorWithValidOperands.types b/tests/baselines/reference/inOperatorWithValidOperands.types index f621c3bf55d..27ba22055bf 100644 --- a/tests/baselines/reference/inOperatorWithValidOperands.types +++ b/tests/baselines/reference/inOperatorWithValidOperands.types @@ -67,3 +67,40 @@ function foo(t: T) { >x : any >t : T } + +interface X { x: number } +>X : X +>x : number + +interface Y { y: number } +>Y : Y +>y : number + +var c1: X | Y; +>c1 : X | Y +>X : X +>Y : Y + +var c2: X; +>c2 : X +>X : X + +var c3: Y; +>c3 : Y +>Y : Y + +var rc1 = x in c1; +>rc1 : boolean +>x in c1 : boolean +>x : any +>c1 : X | Y + +var rc2 = x in (c2 || c3); +>rc2 : boolean +>x in (c2 || c3) : boolean +>x : any +>(c2 || c3) : X | Y +>c2 || c3 : X | Y +>c2 : X +>c3 : Y +