diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a5a904ab3a9..a447b67b336 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4732,12 +4732,11 @@ namespace ts { } function cloneTypePredicate(predicate: TypePredicate, mapper: TypeMapper): ThisTypePredicate | IdentifierTypePredicate { - if (predicate.kind === TypePredicateKind.Identifier) { - const identifierPredicate = predicate as IdentifierTypePredicate; + if (isIdentifierTypePredicate(predicate)) { return { kind: TypePredicateKind.Identifier, - parameterName: identifierPredicate.parameterName, - parameterIndex: identifierPredicate.parameterIndex, + parameterName: predicate.parameterName, + parameterIndex: predicate.parameterIndex, type: instantiateType(predicate.type, mapper) } as IdentifierTypePredicate; } @@ -4751,18 +4750,14 @@ namespace ts { function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature { let freshTypeParameters: TypeParameter[]; - let freshTypePredicate: ThisTypePredicate | IdentifierTypePredicate; if (signature.typeParameters && !eraseTypeParameters) { freshTypeParameters = instantiateList(signature.typeParameters, mapper, instantiateTypeParameter); mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper); } - if (signature.typePredicate) { - freshTypePredicate = cloneTypePredicate(signature.typePredicate, mapper); - } const result = createSignature(signature.declaration, freshTypeParameters, instantiateList(signature.parameters, mapper, instantiateSymbol), instantiateType(signature.resolvedReturnType, mapper), - freshTypePredicate, + signature.typePredicate && cloneTypePredicate(signature.typePredicate, mapper), signature.minArgumentCount, signature.hasRestParameter, signature.hasStringLiterals); result.target = signature; result.mapper = mapper; @@ -6752,7 +6747,7 @@ namespace ts { const predicate = signature.typePredicate; if (isIdentifierTypePredicate(predicate)) { if (expr.arguments[predicate.parameterIndex] && - getSymbolAtLocation(expr.arguments[predicate.parameterIndex]) === symbol) { + getSymbolAtTypePredicatePosition(expr.arguments[predicate.parameterIndex]) === symbol) { return getNarrowedType(type, predicate.type, assumeTrue); } } @@ -6762,18 +6757,28 @@ namespace ts { if (expression.kind === SyntaxKind.ElementAccessExpression || expression.kind === SyntaxKind.PropertyAccessExpression) { const accessExpression = expression as ElementAccessExpression | PropertyAccessExpression; const possibleIdentifier = skipParenthesizedNodes(accessExpression.expression); - if (possibleIdentifier.kind === SyntaxKind.Identifier && getSymbolAtLocation(possibleIdentifier) === symbol) { + if (possibleIdentifier.kind === SyntaxKind.Identifier && getSymbolAtTypePredicatePosition(possibleIdentifier) === symbol) { return getNarrowedType(type, predicate.type, assumeTrue); } } } return type; + } - function skipParenthesizedNodes(expression: Expression): Expression { - while (expression.kind === SyntaxKind.ParenthesizedExpression) { - expression = (expression as ParenthesizedExpression).expression; - } - return expression; + function skipParenthesizedNodes(expression: Expression): Expression { + while (expression.kind === SyntaxKind.ParenthesizedExpression) { + expression = (expression as ParenthesizedExpression).expression; + } + return expression; + } + + function getSymbolAtTypePredicatePosition(expr: Expression): Symbol { + expr = skipParenthesizedNodes(expr); + switch (expr.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.QualifiedName: + return getSymbolOfEntityNameOrPropertyAccessExpression(expr as Node as (EntityName | PropertyAccessExpression)); } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index f48a37968ca..081bfc2385d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1632,7 +1632,7 @@ "category": "Error", "code": 2517 }, - "A this based type guard is not assignable to a parameter based type guard": { + "A 'this'-based type guard is not assignable to a parameter-based type guard": { "category": "Error", "code": 2518 }, diff --git a/tests/baselines/reference/typeGuardFunctionOfFormThis.js b/tests/baselines/reference/typeGuardFunctionOfFormThis.js index 16ac876be8c..bc91defc244 100644 --- a/tests/baselines/reference/typeGuardFunctionOfFormThis.js +++ b/tests/baselines/reference/typeGuardFunctionOfFormThis.js @@ -132,7 +132,8 @@ a.isFollower = mimic.isFollower; if (mimic.isFollower()) { mimic.follow(); mimic.isFollower = a.isFollower; -} +} + //// [typeGuardFunctionOfFormThis.js] var __extends = (this && this.__extends) || function (d, b) { @@ -279,3 +280,58 @@ if (mimic.isFollower()) { mimic.follow(); mimic.isFollower = a.isFollower; } + + +//// [typeGuardFunctionOfFormThis.d.ts] +declare class RoyalGuard { + isLeader(): this is LeadGuard; + isFollower(): this is FollowerGuard; +} +declare class LeadGuard extends RoyalGuard { + lead(): void; +} +declare class FollowerGuard extends RoyalGuard { + follow(): void; +} +declare let a: RoyalGuard; +interface GuardInterface extends RoyalGuard { +} +declare let b: GuardInterface; +declare var holder2: { + a: RoyalGuard; +}; +declare class ArrowGuard { + isElite: () => this is ArrowElite; + isMedic: () => this is ArrowMedic; +} +declare class ArrowElite extends ArrowGuard { + defend(): void; +} +declare class ArrowMedic extends ArrowGuard { + heal(): void; +} +declare let guard: ArrowGuard; +interface Supplies { + spoiled: boolean; +} +interface Sundries { + broken: boolean; +} +interface Crate { + contents: T; + volume: number; + isSupplies(): this is Crate; + isSundries(): this is Crate; +} +declare let crate: Crate<{}>; +declare class MimicGuard { + isLeader(): this is MimicLeader; + isFollower(): this is MimicFollower; +} +declare class MimicLeader extends MimicGuard { + lead(): void; +} +declare class MimicFollower extends MimicGuard { + follow(): void; +} +declare let mimic: MimicGuard; diff --git a/tests/baselines/reference/typeGuardFunctionOfFormThis.symbols b/tests/baselines/reference/typeGuardFunctionOfFormThis.symbols index 9bda32c4557..84c07a17f0e 100644 --- a/tests/baselines/reference/typeGuardFunctionOfFormThis.symbols +++ b/tests/baselines/reference/typeGuardFunctionOfFormThis.symbols @@ -369,3 +369,4 @@ if (mimic.isFollower()) { >a : Symbol(a, Decl(typeGuardFunctionOfFormThis.ts, 17, 3)) >isFollower : Symbol(RoyalGuard.isFollower, Decl(typeGuardFunctionOfFormThis.ts, 3, 5)) } + diff --git a/tests/baselines/reference/typeGuardFunctionOfFormThis.types b/tests/baselines/reference/typeGuardFunctionOfFormThis.types index fbe862ab98b..fa5becc7790 100644 --- a/tests/baselines/reference/typeGuardFunctionOfFormThis.types +++ b/tests/baselines/reference/typeGuardFunctionOfFormThis.types @@ -425,3 +425,4 @@ if (mimic.isFollower()) { >a : RoyalGuard >isFollower : () => this is FollowerGuard } + diff --git a/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.errors.txt b/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.errors.txt index 9d2e9fdd48e..605282f19bb 100644 --- a/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.errors.txt +++ b/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.errors.txt @@ -7,9 +7,11 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThisErrors tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThisErrors.ts(27,1): error TS2322: Type '() => this is FollowerGuard' is not assignable to type '() => this is LeadGuard'. Type 'FollowerGuard' is not assignable to type 'LeadGuard'. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThisErrors.ts(29,32): error TS2526: A 'this' type is available only in a non-static member of a class or interface. +tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThisErrors.ts(55,7): error TS2339: Property 'follow' does not exist on type 'RoyalGuard'. +tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThisErrors.ts(58,7): error TS2339: Property 'lead' does not exist on type 'RoyalGuard'. -==== tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThisErrors.ts (5 errors) ==== +==== tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThisErrors.ts (7 errors) ==== class RoyalGuard { isLeader(): this is LeadGuard { return this instanceof LeadGuard; @@ -73,4 +75,17 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThisErrors else { c; holder; + } + + let detached = a.isFollower; + + if (detached()) { + a.follow(); + ~~~~~~ +!!! error TS2339: Property 'follow' does not exist on type 'RoyalGuard'. + } + else { + a.lead(); + ~~~~ +!!! error TS2339: Property 'lead' does not exist on type 'RoyalGuard'. } \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js b/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js index f11716daebd..0495e3cd773 100644 --- a/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js +++ b/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js @@ -48,6 +48,15 @@ if (holder.invalidGuard(c)) { else { c; holder; +} + +let detached = a.isFollower; + +if (detached()) { + a.follow(); +} +else { + a.lead(); } //// [typeGuardFunctionOfFormThisErrors.js] @@ -111,3 +120,33 @@ else { c; holder; } +var detached = a.isFollower; +if (detached()) { + a.follow(); +} +else { + a.lead(); +} + + +//// [typeGuardFunctionOfFormThisErrors.d.ts] +declare class RoyalGuard { + isLeader(): this is LeadGuard; + isFollower(): this is FollowerGuard; +} +declare class LeadGuard extends RoyalGuard { + lead(): void; +} +declare class FollowerGuard extends RoyalGuard { + follow(): void; +} +interface GuardInterface extends RoyalGuard { +} +declare let a: RoyalGuard; +declare let b: GuardInterface; +declare function invalidGuard(c: any): this is number; +declare let c: number | number[]; +declare let holder: { + invalidGuard: (c: any) => this is number; +}; +declare let detached: () => this is FollowerGuard; diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThis.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThis.ts index bc7bbce3816..c053ec55d07 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThis.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThis.ts @@ -1,3 +1,4 @@ +// @declaration: true class RoyalGuard { isLeader(): this is LeadGuard { return this instanceof LeadGuard; @@ -131,4 +132,4 @@ a.isFollower = mimic.isFollower; if (mimic.isFollower()) { mimic.follow(); mimic.isFollower = a.isFollower; -} \ No newline at end of file +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThisErrors.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThisErrors.ts index 2e03781952d..1c449e624f1 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThisErrors.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardFunctionOfFormThisErrors.ts @@ -1,3 +1,4 @@ +// @declaration: true class RoyalGuard { isLeader(): this is LeadGuard { return this instanceof LeadGuard; @@ -47,4 +48,13 @@ if (holder.invalidGuard(c)) { else { c; holder; +} + +let detached = a.isFollower; + +if (detached()) { + a.follow(); +} +else { + a.lead(); } \ No newline at end of file