@@ -3844,44 +3844,285 @@ module ts {
38443844
38453845 // EXPRESSION TYPE CHECKING
38463846
3847- function checkIdentifier ( node : Identifier ) : Type {
3848- function isInTypeQuery ( node : Node ) : boolean {
3849- // TypeScript 1.0 spec (April 2014): 3.6.3
3850- // A type query consists of the keyword typeof followed by an expression.
3851- // The expression is restricted to a single identifier or a sequence of identifiers separated by periods
3852- while ( node ) {
3847+ function getResolvedSymbol ( node : Identifier ) : Symbol {
3848+ var links = getNodeLinks ( node ) ;
3849+ if ( ! links . resolvedSymbol ) {
3850+ links . resolvedSymbol = resolveName ( node , node . text , SymbolFlags . Value | SymbolFlags . ExportValue , Diagnostics . Cannot_find_name_0 , identifierToString ( node ) ) || unknownSymbol ;
3851+ }
3852+ return links . resolvedSymbol ;
3853+ }
3854+
3855+ function isInTypeQuery ( node : Node ) : boolean {
3856+ // TypeScript 1.0 spec (April 2014): 3.6.3
3857+ // A type query consists of the keyword typeof followed by an expression.
3858+ // The expression is restricted to a single identifier or a sequence of identifiers separated by periods
3859+ while ( node ) {
3860+ switch ( node . kind ) {
3861+ case SyntaxKind . TypeQuery :
3862+ return true ;
3863+ case SyntaxKind . Identifier :
3864+ case SyntaxKind . QualifiedName :
3865+ node = node . parent ;
3866+ continue ;
3867+ default :
3868+ return false ;
3869+ }
3870+ }
3871+ Debug . fail ( "should not get here" ) ;
3872+ }
3873+
3874+ // Remove one or more primitive types from a union type
3875+ function subtractPrimitiveTypes ( type : Type , subtractMask : TypeFlags ) : Type {
3876+ if ( type . flags & TypeFlags . Union ) {
3877+ var types = ( < UnionType > type ) . types ;
3878+ if ( forEach ( types , t => t . flags & subtractMask ) ) {
3879+ var newTypes : Type [ ] = [ ] ;
3880+ forEach ( types , t => {
3881+ if ( ! ( t . flags & subtractMask ) ) {
3882+ newTypes . push ( t ) ;
3883+ }
3884+ } ) ;
3885+ return getUnionType ( newTypes ) ;
3886+ }
3887+ }
3888+ return type ;
3889+ }
3890+
3891+ // Check if a given variable is assigned within a given syntax node
3892+ function IsVariableAssignedWithin ( symbol : Symbol , node : Node ) : boolean {
3893+ var links = getNodeLinks ( node ) ;
3894+ if ( links . assignmentChecks ) {
3895+ var cachedResult = links . assignmentChecks [ symbol . id ] ;
3896+ if ( cachedResult !== undefined ) {
3897+ return cachedResult ;
3898+ }
3899+ }
3900+ else {
3901+ links . assignmentChecks = { } ;
3902+ }
3903+ return links . assignmentChecks [ symbol . id ] = isAssignedIn ( node ) ;
3904+
3905+ function isAssignedInBinaryExpression ( node : BinaryExpression ) {
3906+ if ( node . operator >= SyntaxKind . FirstAssignment && node . operator <= SyntaxKind . LastAssignment ) {
3907+ var n = node . left ;
3908+ while ( n . kind === SyntaxKind . ParenExpression ) {
3909+ n = ( < ParenExpression > n ) . expression ;
3910+ }
3911+ if ( n . kind === SyntaxKind . Identifier && getResolvedSymbol ( < Identifier > n ) === symbol ) {
3912+ return true ;
3913+ }
3914+ }
3915+ return forEachChild ( node , isAssignedIn ) ;
3916+ }
3917+
3918+ function isAssignedInVariableDeclaration ( node : VariableDeclaration ) {
3919+ if ( getSymbolOfNode ( node ) === symbol && node . initializer ) {
3920+ return true ;
3921+ }
3922+ return forEachChild ( node , isAssignedIn ) ;
3923+ }
3924+
3925+ function isAssignedIn ( node : Node ) : boolean {
3926+ switch ( node . kind ) {
3927+ case SyntaxKind . BinaryExpression :
3928+ return isAssignedInBinaryExpression ( < BinaryExpression > node ) ;
3929+ case SyntaxKind . VariableDeclaration :
3930+ return isAssignedInVariableDeclaration ( < VariableDeclaration > node ) ;
3931+ case SyntaxKind . ArrayLiteral :
3932+ case SyntaxKind . ObjectLiteral :
3933+ case SyntaxKind . PropertyAccess :
3934+ case SyntaxKind . IndexedAccess :
3935+ case SyntaxKind . CallExpression :
3936+ case SyntaxKind . NewExpression :
3937+ case SyntaxKind . TypeAssertion :
3938+ case SyntaxKind . ParenExpression :
3939+ case SyntaxKind . PrefixOperator :
3940+ case SyntaxKind . PostfixOperator :
3941+ case SyntaxKind . ConditionalExpression :
3942+ case SyntaxKind . Block :
3943+ case SyntaxKind . VariableStatement :
3944+ case SyntaxKind . ExpressionStatement :
3945+ case SyntaxKind . IfStatement :
3946+ case SyntaxKind . DoStatement :
3947+ case SyntaxKind . WhileStatement :
3948+ case SyntaxKind . ForStatement :
3949+ case SyntaxKind . ForInStatement :
3950+ case SyntaxKind . ReturnStatement :
3951+ case SyntaxKind . WithStatement :
3952+ case SyntaxKind . SwitchStatement :
3953+ case SyntaxKind . CaseClause :
3954+ case SyntaxKind . DefaultClause :
3955+ case SyntaxKind . LabeledStatement :
3956+ case SyntaxKind . ThrowStatement :
3957+ case SyntaxKind . TryStatement :
3958+ case SyntaxKind . TryBlock :
3959+ case SyntaxKind . CatchBlock :
3960+ case SyntaxKind . FinallyBlock :
3961+ return forEachChild ( node , isAssignedIn ) ;
3962+ }
3963+ return false ;
3964+ }
3965+ }
3966+
3967+ // Get the narrowed type of a given symbol at a given location
3968+ function getNarrowedTypeOfSymbol ( symbol : Symbol , node : Node ) {
3969+ var type = getTypeOfSymbol ( symbol ) ;
3970+ // Only narrow when symbol is variable of a non-primitive type
3971+ if ( symbol . flags & SymbolFlags . Variable && isTypeAnyOrObjectOrTypeParameter ( type ) ) {
3972+ while ( true ) {
3973+ var child = node ;
3974+ node = node . parent ;
3975+ // Stop at containing function or module block
3976+ if ( ! node || node . kind === SyntaxKind . FunctionBlock || node . kind === SyntaxKind . ModuleBlock ) {
3977+ break ;
3978+ }
3979+ var narrowedType = type ;
38533980 switch ( node . kind ) {
3854- case SyntaxKind . TypeQuery :
3855- return true ;
3856- case SyntaxKind . Identifier :
3857- case SyntaxKind . QualifiedName :
3858- node = node . parent ;
3859- continue ;
3860- default :
3861- return false ;
3981+ case SyntaxKind . IfStatement :
3982+ // In a branch of an if statement, narrow based on controlling expression
3983+ if ( child !== ( < IfStatement > node ) . expression ) {
3984+ narrowedType = narrowType ( type , ( < IfStatement > node ) . expression , child === ( < IfStatement > node ) . thenStatement ) ;
3985+ }
3986+ break ;
3987+ case SyntaxKind . ConditionalExpression :
3988+ // In a branch of a conditional expression, narrow based on controlling condition
3989+ if ( child !== ( < ConditionalExpression > node ) . condition ) {
3990+ narrowedType = narrowType ( type , ( < ConditionalExpression > node ) . condition , child === ( < ConditionalExpression > node ) . whenTrue ) ;
3991+ }
3992+ break ;
3993+ case SyntaxKind . BinaryExpression :
3994+ // In the right operand of an && or ||, narrow based on left operand
3995+ if ( child === ( < BinaryExpression > node ) . right ) {
3996+ if ( ( < BinaryExpression > node ) . operator === SyntaxKind . AmpersandAmpersandToken ) {
3997+ narrowedType = narrowType ( type , ( < BinaryExpression > node ) . left , true ) ;
3998+ }
3999+ else if ( ( < BinaryExpression > node ) . operator === SyntaxKind . BarBarToken ) {
4000+ narrowedType = narrowType ( type , ( < BinaryExpression > node ) . left , false ) ;
4001+ }
4002+ }
4003+ break ;
4004+ }
4005+ // Only use narrowed type if construct contains no assignments to variable
4006+ if ( narrowedType !== type && ! IsVariableAssignedWithin ( symbol , node ) ) {
4007+ type = narrowedType ;
38624008 }
38634009 }
3864- Debug . fail ( "should not get here" ) ;
4010+ }
4011+ return type ;
4012+
4013+ function narrowTypeByEquality ( type : Type , expr : BinaryExpression , assumeTrue : boolean ) : Type {
4014+ var left = < UnaryExpression > expr . left ;
4015+ var right = < LiteralExpression > expr . right ;
4016+ // Check that we have 'typeof <symbol>' on the left and string literal on the right
4017+ if ( left . kind !== SyntaxKind . PrefixOperator || left . operator !== SyntaxKind . TypeOfKeyword ||
4018+ left . operand . kind !== SyntaxKind . Identifier || right . kind !== SyntaxKind . StringLiteral ||
4019+ getResolvedSymbol ( < Identifier > left . operand ) !== symbol ) {
4020+ return type ;
4021+ }
4022+ var t = right . text ;
4023+ var checkType : Type = t === "string" ? stringType : t === "number" ? numberType : t === "boolean" ? booleanType : emptyObjectType ;
4024+ if ( expr . operator === SyntaxKind . ExclamationEqualsEqualsToken ) {
4025+ assumeTrue = ! assumeTrue ;
4026+ }
4027+ if ( assumeTrue ) {
4028+ // The assumed result is true. If check was for a primitive type, that type is the narrowed type. Otherwise we can
4029+ // remove the primitive types from the narrowed type.
4030+ return checkType === emptyObjectType ? subtractPrimitiveTypes ( type , TypeFlags . String | TypeFlags . Number | TypeFlags . Boolean ) : checkType ;
4031+ }
4032+ else {
4033+ // The assumed result is false. If check was for a primitive type we can remove that type from the narrowed type.
4034+ // Otherwise we don't have enough information to do anything.
4035+ return checkType === emptyObjectType ? type : subtractPrimitiveTypes ( type , checkType . flags ) ;
4036+ }
38654037 }
38664038
3867- var symbol = resolveName ( node , node . text , SymbolFlags . Value | SymbolFlags . ExportValue , Diagnostics . Cannot_find_name_0 , identifierToString ( node ) ) ;
3868- if ( ! symbol ) {
3869- symbol = unknownSymbol ;
4039+ function narrowTypeByAnd ( type : Type , expr : BinaryExpression , assumeTrue : boolean ) : Type {
4040+ if ( assumeTrue ) {
4041+ // The assumed result is true, therefore we narrow assuming each operand to be true.
4042+ return narrowType ( narrowType ( type , expr . left , true ) , expr . right , true ) ;
4043+ }
4044+ else {
4045+ // The assumed result is true. This means either the first operand was false, or the first operand was true
4046+ // and the second operand was false. We narrow with those assumptions and union the two resulting types.
4047+ return getUnionType ( [ narrowType ( type , expr . left , false ) , narrowType ( narrowType ( type , expr . left , true ) , expr . right , false ) ] ) ;
4048+ }
38704049 }
38714050
4051+ function narrowTypeByOr ( type : Type , expr : BinaryExpression , assumeTrue : boolean ) : Type {
4052+ if ( assumeTrue ) {
4053+ // The assumed result is true. This means either the first operand was true, or the first operand was false
4054+ // and the second operand was true. We narrow with those assumptions and union the two resulting types.
4055+ return getUnionType ( [ narrowType ( type , expr . left , true ) , narrowType ( narrowType ( type , expr . left , false ) , expr . right , true ) ] ) ;
4056+ }
4057+ else {
4058+ // The assumed result is false, therefore we narrow assuming each operand to be false.
4059+ return narrowType ( narrowType ( type , expr . left , false ) , expr . right , false ) ;
4060+ }
4061+ }
4062+
4063+ function narrowTypeByInstanceof ( type : Type , expr : BinaryExpression , assumeTrue : boolean ) : Type {
4064+ // Check that we have variable symbol on the left
4065+ if ( expr . left . kind !== SyntaxKind . Identifier || getResolvedSymbol ( < Identifier > expr . left ) !== symbol ) {
4066+ return type ;
4067+ }
4068+ // Check that right operand is a function type with a prototype property
4069+ var rightType = checkExpression ( expr . right ) ;
4070+ if ( ! isTypeSubtypeOf ( rightType , globalFunctionType ) ) {
4071+ return type ;
4072+ }
4073+ var prototypeProperty = getPropertyOfType ( getApparentType ( rightType ) , "prototype" ) ;
4074+ if ( ! prototypeProperty ) {
4075+ return type ;
4076+ }
4077+ var prototypeType = getTypeOfSymbol ( prototypeProperty ) ;
4078+ // Narrow to type of prototype property if it is a subtype of current type
4079+ return isTypeSubtypeOf ( prototypeType , type ) ? prototypeType : type ;
4080+ }
4081+
4082+ // Narrow the given type based on the given expression having the assumed boolean value
4083+ function narrowType ( type : Type , expr : Expression , assumeTrue : boolean ) : Type {
4084+ switch ( expr . kind ) {
4085+ case SyntaxKind . ParenExpression :
4086+ return narrowType ( type , ( < ParenExpression > expr ) . expression , assumeTrue ) ;
4087+ case SyntaxKind . BinaryExpression :
4088+ var operator = ( < BinaryExpression > expr ) . operator ;
4089+ if ( operator === SyntaxKind . EqualsEqualsEqualsToken || operator === SyntaxKind . ExclamationEqualsEqualsToken ) {
4090+ return narrowTypeByEquality ( type , < BinaryExpression > expr , assumeTrue ) ;
4091+ }
4092+ else if ( operator === SyntaxKind . AmpersandAmpersandToken ) {
4093+ return narrowTypeByAnd ( type , < BinaryExpression > expr , assumeTrue ) ;
4094+ }
4095+ else if ( operator === SyntaxKind . BarBarToken ) {
4096+ return narrowTypeByOr ( type , < BinaryExpression > expr , assumeTrue ) ;
4097+ }
4098+ else if ( operator === SyntaxKind . InstanceOfKeyword ) {
4099+ return narrowTypeByInstanceof ( type , < BinaryExpression > expr , assumeTrue ) ;
4100+ }
4101+ break ;
4102+ case SyntaxKind . PrefixOperator :
4103+ if ( ( < UnaryExpression > expr ) . operator === SyntaxKind . ExclamationToken ) {
4104+ return narrowType ( type , ( < UnaryExpression > expr ) . operand , ! assumeTrue ) ;
4105+ }
4106+ break ;
4107+ }
4108+ return type ;
4109+ }
4110+ }
4111+
4112+ function checkIdentifier ( node : Identifier ) : Type {
4113+ var symbol = getResolvedSymbol ( node ) ;
4114+
38724115 if ( symbol . flags & SymbolFlags . Import ) {
38734116 // Mark the import as referenced so that we emit it in the final .js file.
38744117 // exception: identifiers that appear in type queries
38754118 getSymbolLinks ( symbol ) . referenced = ! isInTypeQuery ( node ) ;
38764119 }
38774120
3878- getNodeLinks ( node ) . resolvedSymbol = symbol ;
3879-
38804121 checkCollisionWithCapturedSuperVariable ( node , node ) ;
38814122 checkCollisionWithCapturedThisVariable ( node , node ) ;
38824123 checkCollisionWithIndexVariableInGeneratedCode ( node , node ) ;
38834124
3884- return getTypeOfSymbol ( getExportSymbolOfValueSymbolIfExported ( symbol ) ) ;
4125+ return getNarrowedTypeOfSymbol ( getExportSymbolOfValueSymbolIfExported ( symbol ) , node ) ;
38854126 }
38864127
38874128 function captureLexicalThis ( node : Node , container : Node ) : void {
@@ -5134,8 +5375,8 @@ module ts {
51345375 return numberType ;
51355376 }
51365377
5137- function isTypeAnyTypeObjectTypeOrTypeParameter ( type : Type ) : boolean {
5138- return type === anyType || ( ( type . flags & ( TypeFlags . ObjectType | TypeFlags . TypeParameter ) ) !== 0 ) ;
5378+ function isTypeAnyOrObjectOrTypeParameter ( type : Type ) : boolean {
5379+ return ( type . flags & ( TypeFlags . Any | TypeFlags . ObjectType | TypeFlags . TypeParameter ) ) !== 0 ;
51395380 }
51405381
51415382 function checkInstanceOfExpression ( node : BinaryExpression , leftType : Type , rightType : Type ) : Type {
@@ -5144,7 +5385,7 @@ module ts {
51445385 // and the right operand to be of type Any or a subtype of the 'Function' interface type.
51455386 // The result is always of the Boolean primitive type.
51465387 // NOTE: do not raise error if leftType is unknown as related error was already reported
5147- if ( leftType !== unknownType && ! isTypeAnyTypeObjectTypeOrTypeParameter ( leftType ) ) {
5388+ if ( leftType !== unknownType && ! isTypeAnyOrObjectOrTypeParameter ( leftType ) ) {
51485389 error ( node . left , Diagnostics . The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter ) ;
51495390 }
51505391 // NOTE: do not raise error if right is unknown as related error was already reported
@@ -5162,7 +5403,7 @@ module ts {
51625403 if ( leftType !== anyType && leftType !== stringType && leftType !== numberType ) {
51635404 error ( node . left , Diagnostics . The_left_hand_side_of_an_in_expression_must_be_of_types_any_string_or_number ) ;
51645405 }
5165- if ( ! isTypeAnyTypeObjectTypeOrTypeParameter ( rightType ) ) {
5406+ if ( ! isTypeAnyOrObjectOrTypeParameter ( rightType ) ) {
51665407 error ( node . right , Diagnostics . The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter ) ;
51675408 }
51685409 return booleanType ;
@@ -6338,7 +6579,7 @@ module ts {
63386579 var exprType = checkExpression ( node . expression ) ;
63396580 // unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved
63406581 // in this case error about missing name is already reported - do not report extra one
6341- if ( ! isTypeAnyTypeObjectTypeOrTypeParameter ( exprType ) && exprType !== unknownType ) {
6582+ if ( ! isTypeAnyOrObjectOrTypeParameter ( exprType ) && exprType !== unknownType ) {
63426583 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 ) ;
63436584 }
63446585
0 commit comments