From be6e341d2afcaf0f00b13212db896991a2f339b7 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 7 Dec 2015 18:16:05 -0800 Subject: [PATCH] Fix narrowing, interfaces. Expose issue with generic instantiation --- src/compiler/binder.ts | 13 +++- src/compiler/checker.ts | 40 ++++++------ src/compiler/types.ts | 2 +- .../typeGuards/typeGuardFunctionOfFormThis.ts | 6 ++ .../typeGuards/typeGuardOfFormThisMember.ts | 62 ++++++++++++++++++- 5 files changed, 101 insertions(+), 22 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 7561783d139..837260e5e1b 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1189,7 +1189,8 @@ namespace ts { case SyntaxKind.ThisType: seenThisKeyword = true; return; - + case SyntaxKind.TypePredicate: + return checkTypePredicate(node as TypePredicateNode); case SyntaxKind.TypeParameter: return declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); case SyntaxKind.Parameter: @@ -1275,6 +1276,16 @@ namespace ts { } } + function checkTypePredicate(node: TypePredicateNode) { + if (node.parameterName && node.parameterName.kind === SyntaxKind.Identifier) { + checkStrictModeIdentifier(node.parameterName as Identifier); + } + if (node.parameterName && node.parameterName.kind === SyntaxKind.ThisType) { + seenThisKeyword = true; + } + bind(node.type); + } + function bindSourceFileIfExternalModule() { setExportContextFlag(file); if (isExternalModule(file)) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a1d13faff5a..02ddd7b26cf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2634,7 +2634,13 @@ namespace ts { // During a normal type check we'll never get to here with a property assignment (the check of the containing // object literal uses a different path). We exclude widening only so that language services and type verification // tools see the actual type. - return declaration.kind !== SyntaxKind.PropertyAssignment ? getWidenedType(type) : type; + if (declaration.kind === SyntaxKind.PropertyAssignment) { + return type; + } + if (type.flags & TypeFlags.PredicateType && (declaration.kind === SyntaxKind.PropertyDeclaration || declaration.kind === SyntaxKind.PropertySignature)) { + return type; + } + return getWidenedType(type); } // Rest parameters default to type any[], other parameters default to type any @@ -4602,7 +4608,7 @@ namespace ts { } function getPredicateType(node: TypePredicateNode): Type { - if (!(node.parent.kind === SyntaxKind.PropertyDeclaration || node.parent.kind === SyntaxKind.GetAccessor)) { + if (!(node.parent.kind === SyntaxKind.PropertyDeclaration || node.parent.kind === SyntaxKind.PropertySignature || node.parent.kind === SyntaxKind.GetAccessor)) { return booleanType; } else { @@ -6008,6 +6014,9 @@ namespace ts { function getWidenedType(type: Type): Type { if (type.flags & TypeFlags.RequiresWidening) { + if (type.flags & TypeFlags.PredicateType) { + return booleanType; + } if (type.flags & (TypeFlags.Undefined | TypeFlags.Null)) { return anyType; } @@ -11078,8 +11087,9 @@ namespace ts { } switch (node.parent.kind) { case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: case SyntaxKind.GetAccessor: - return node === (node.parent).type; + return node === (node.parent as (PropertyDeclaration | GetAccessorDeclaration | PropertySignature)).type; } return false; } @@ -11104,12 +11114,8 @@ namespace ts { if (node.type.kind === SyntaxKind.TypePredicate) { const typePredicate = getSignatureFromDeclaration(node).typePredicate; const typePredicateNode = node.type; + checkSourceElement(typePredicateNode); if (isIdentifierTypePredicate(typePredicate)) { - if (!isInLegalTypePredicatePosition(typePredicateNode)) { - error(typePredicateNode, - Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); - return; - } if (typePredicate.parameterIndex >= 0) { if (node.parameters[typePredicate.parameterIndex].dotDotDotToken) { error(typePredicateNode.parameterName, @@ -11157,15 +11163,6 @@ namespace ts { } } } - else { - if (!isInLegalThisTypePredicatePosition(typePredicateNode)) { - error(typePredicateNode, - Diagnostics.A_this_based_type_predicate_is_only_allowed_in_class_or_interface_members_get_accessors_or_return_type_positions_for_functions_and_methods); - return; - } - // Reuse this type diagnostics on the this type node to determine if a this type predicate is valid - getTypeFromThisTypeNode(typePredicateNode.parameterName as ThisTypeNode); - } } else { checkSourceElement(node.type); @@ -14295,8 +14292,13 @@ namespace ts { if (node.parameterName.kind === SyntaxKind.Identifier && !isInLegalTypePredicatePosition(node)) { error(node, Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); } - else if (node.parameterName.kind === SyntaxKind.ThisType && (!isInLegalThisTypePredicatePosition(node) || getTypeFromThisTypeNode(node.parameterName as ThisTypeNode) === unknownType)) { - error(node, Diagnostics.A_this_based_type_predicate_is_only_allowed_in_class_or_interface_members_get_accessors_or_return_type_positions_for_functions_and_methods); + else if (node.parameterName.kind === SyntaxKind.ThisType) { + if (!isInLegalThisTypePredicatePosition(node)) { + error(node, Diagnostics.A_this_based_type_predicate_is_only_allowed_in_class_or_interface_members_get_accessors_or_return_type_positions_for_functions_and_methods); + } + else { + getTypeFromThisTypeNode(node.parameterName as ThisTypeNode); + } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d8e6291ac02..54eb4f449dd 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2120,7 +2120,7 @@ namespace ts { UnionOrIntersection = Union | Intersection, StructuredType = ObjectType | Union | Intersection, /* @internal */ - RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral, + RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral | PredicateType, /* @internal */ PropagatingFlags = ContainsUndefinedOrNull | ContainsObjectLiteral | ContainsAnyFunctionType } diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThis.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThis.ts index c053ec55d07..aadbf3cd9c5 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThis.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThis.ts @@ -133,3 +133,9 @@ if (mimic.isFollower()) { mimic.follow(); mimic.isFollower = a.isFollower; } + + +interface MimicGuardInterface { + isLeader(): this is LeadGuard; + isFollower(): this is FollowerGuard; +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts index 1c714ff7d5a..182d475b815 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts @@ -40,4 +40,64 @@ namespace Test { else if (file.isNetworked) { file.host; } -} \ No newline at end of file + + interface GenericLeadGuard extends GenericGuard { + lead(): void; + } + + interface GenericFollowerGuard extends GenericGuard { + follow(): void; + } + + interface GenericGuard { + target: T; + isLeader: this is (GenericLeadGuard); + isFollower: this is GenericFollowerGuard; + } + + let guard: GenericGuard; + if (guard.isLeader) { + guard.lead(); + } + else if (guard.isFollower) { + guard.follow(); + } + + interface SpecificGuard { + isMoreSpecific: this is MoreSpecificGuard; + } + + interface MoreSpecificGuard extends SpecificGuard { + do(): void; + } + + let general: SpecificGuard; + if (general.isMoreSpecific) { + general.do(); + } + + + class doThing { + constructor(private x: T) {} + isThing(x: any): x is doThing { + return true; + } + + } + + let z: doThing<{}> = new doThing({x: 10}); + let z1 = new doThing({x: 10}); + if (z1.isThing(z)) { + z; + } +} + +function f(g: (x: number) => void) { + + +} + +f(function(x) { + + +}) \ No newline at end of file