diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9c9474eed4d..a1d13faff5a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1670,10 +1670,16 @@ namespace ts { function writeType(type: Type, flags: TypeFormatFlags) { // Write undefined/null type as any if (type.flags & TypeFlags.Intrinsic) { - // Special handling for unknown / resolving types, they should show up as any and not unknown or __resolving - writer.writeKeyword(!(globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike) && isTypeAny(type) - ? "any" - : (type).intrinsicName); + if (type.flags & TypeFlags.PredicateType) { + buildTypePredicateDisplay(writer, (type as PredicateType).predicate); + buildTypeDisplay((type as PredicateType).predicate.type, writer, enclosingDeclaration, flags, symbolStack); + } + else { + // Special handling for unknown / resolving types, they should show up as any and not unknown or __resolving + writer.writeKeyword(!(globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike) && isTypeAny(type) + ? "any" + : (type).intrinsicName); + } } else if (type.flags & TypeFlags.ThisType) { if (inObjectTypeLiteral) { @@ -2046,6 +2052,18 @@ namespace ts { writePunctuation(writer, SyntaxKind.CloseParenToken); } + function buildTypePredicateDisplay(writer: SymbolWriter, predicate: TypePredicate) { + if (isIdentifierTypePredicate(predicate)) { + writer.writeParameter(predicate.parameterName); + } + else { + writeKeyword(writer, SyntaxKind.ThisKeyword); + } + writeSpace(writer); + writeKeyword(writer, SyntaxKind.IsKeyword); + writeSpace(writer); + } + function buildReturnTypeDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { if (flags & TypeFormatFlags.WriteArrowStyleSignature) { writeSpace(writer); @@ -2059,15 +2077,7 @@ namespace ts { let returnType: Type; const predicate = signature.typePredicate; if (predicate) { - if (isIdentifierTypePredicate(predicate)) { - writer.writeParameter(predicate.parameterName); - } - else { - writeKeyword(writer, SyntaxKind.ThisKeyword); - } - writeSpace(writer); - writeKeyword(writer, SyntaxKind.IsKeyword); - writeSpace(writer); + buildTypePredicateDisplay(writer, predicate); returnType = predicate.type; } else { @@ -3850,6 +3860,24 @@ namespace ts { return false; } + function createTypePredicateFromTypePredicateNode(node: TypePredicateNode): IdentifierTypePredicate | ThisTypePredicate { + if (node.parameterName.kind === SyntaxKind.Identifier) { + const parameterName = node.parameterName as Identifier; + return { + kind: TypePredicateKind.Identifier, + parameterName: parameterName ? parameterName.text : undefined, + parameterIndex: parameterName ? getTypePredicateParameterIndex((node.parent as SignatureDeclaration).parameters, parameterName) : undefined, + type: getTypeFromTypeNode(node.type) + } as IdentifierTypePredicate; + } + else { + return { + kind: TypePredicateKind.This, + type: getTypeFromTypeNode(node.type) + } as ThisTypePredicate; + } + } + function getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature { const links = getNodeLinks(declaration); if (!links.resolvedSignature) { @@ -3897,22 +3925,7 @@ namespace ts { else if (declaration.type) { returnType = getTypeFromTypeNode(declaration.type); if (declaration.type.kind === SyntaxKind.TypePredicate) { - const typePredicateNode = declaration.type as TypePredicateNode; - if (typePredicateNode.parameterName.kind === SyntaxKind.Identifier) { - const parameterName = typePredicateNode.parameterName as Identifier; - typePredicate = { - kind: TypePredicateKind.Identifier, - parameterName: parameterName ? parameterName.text : undefined, - parameterIndex: parameterName ? getTypePredicateParameterIndex(declaration.parameters, parameterName) : undefined, - type: getTypeFromTypeNode(typePredicateNode.type) - }; - } - else { - typePredicate = { - kind: TypePredicateKind.This, - type: getTypeFromTypeNode(typePredicateNode.type) - } as ThisTypePredicate; - } + typePredicate = createTypePredicateFromTypePredicateNode(declaration.type as TypePredicateNode); } } else { @@ -4588,6 +4601,26 @@ namespace ts { return links.resolvedType; } + function getPredicateType(node: TypePredicateNode): Type { + if (!(node.parent.kind === SyntaxKind.PropertyDeclaration || node.parent.kind === SyntaxKind.GetAccessor)) { + return booleanType; + } + else { + const type = createType(TypeFlags.Boolean | TypeFlags.PredicateType) as PredicateType; + type.symbol = getSymbolOfNode(node); + type.predicate = createTypePredicateFromTypePredicateNode(node) as ThisTypePredicate; + return type; + } + } + + function getTypeFromPredicateTypeNode(node: TypePredicateNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getPredicateType(node); + } + return links.resolvedType; + } + function getTypeFromTypeNode(node: TypeNode): Type { switch (node.kind) { case SyntaxKind.AnyKeyword: @@ -4609,7 +4642,7 @@ namespace ts { case SyntaxKind.TypeReference: return getTypeFromTypeReference(node); case SyntaxKind.TypePredicate: - return booleanType; + return getTypeFromPredicateTypeNode(node); case SyntaxKind.ExpressionWithTypeArguments: return getTypeFromTypeReference(node); case SyntaxKind.TypeQuery: @@ -4998,6 +5031,7 @@ namespace ts { if (relation === assignableRelation) { if (isTypeAny(source)) return Ternary.True; if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True; + if (source.flags & TypeFlags.Boolean && target.flags & TypeFlags.Boolean && (source.flags & TypeFlags.PredicateType || target.flags & TypeFlags.PredicateType)) return Ternary.True; } if (source.flags & TypeFlags.FreshObjectLiteral) { @@ -6747,20 +6781,37 @@ namespace ts { } const predicate = signature.typePredicate; if (isIdentifierTypePredicate(predicate)) { - if (expr.arguments[predicate.parameterIndex] && - getSymbolAtTypePredicatePosition(expr.arguments[predicate.parameterIndex]) === symbol) { + const callExpression = expr as CallExpression; + if (callExpression.arguments[predicate.parameterIndex] && + getSymbolAtTypePredicatePosition(callExpression.arguments[predicate.parameterIndex]) === symbol) { return getNarrowedType(type, predicate.type, assumeTrue); } } else { const expression = skipParenthesizedNodes(expr.expression); + return narrowTypeByThisTypePredicate(type, predicate, expression, assumeTrue); + } + return type; + } - 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 && getSymbolAtTypePredicatePosition(possibleIdentifier) === symbol) { - return getNarrowedType(type, predicate.type, assumeTrue); - } + function narrowTypeByTypePredicateMember(type: Type, expr: ElementAccessExpression | PropertyAccessExpression, assumeTrue: boolean): Type { + if (type.flags & TypeFlags.Any) { + return type; + } + const memberType = getTypeOfExpression(expr); + if (!(memberType.flags & TypeFlags.PredicateType)) { + return type; + } + + return narrowTypeByThisTypePredicate(type, (memberType as PredicateType).predicate, expr, assumeTrue); + } + + function narrowTypeByThisTypePredicate(type: Type, predicate: ThisTypePredicate, expression: Expression, assumeTrue: boolean): Type { + 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 && getSymbolAtTypePredicatePosition(possibleIdentifier) === symbol) { + return getNarrowedType(type, predicate.type, assumeTrue); } } return type; @@ -6811,6 +6862,9 @@ namespace ts { return narrowType(type, (expr).operand, !assumeTrue); } break; + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: + return narrowTypeByTypePredicateMember(type, expr as (ElementAccessExpression | PropertyAccessExpression), assumeTrue); } return type; } @@ -11018,6 +11072,18 @@ namespace ts { return false; } + function isInLegalThisTypePredicatePosition(node: Node): boolean { + if (isInLegalTypePredicatePosition(node)) { + return true; + } + switch (node.parent.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.GetAccessor: + return node === (node.parent).type; + } + return false; + } + function checkSignatureDeclaration(node: SignatureDeclaration) { // Grammar checking if (node.kind === SyntaxKind.IndexSignature) { @@ -11038,63 +11104,67 @@ namespace ts { if (node.type.kind === SyntaxKind.TypePredicate) { const typePredicate = getSignatureFromDeclaration(node).typePredicate; const typePredicateNode = node.type; - if (isInLegalTypePredicatePosition(typePredicateNode)) { - if (isIdentifierTypePredicate(typePredicate)) { - if (typePredicate.parameterIndex >= 0) { - if (node.parameters[typePredicate.parameterIndex].dotDotDotToken) { - error(typePredicateNode.parameterName, - Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter); - } - else { - checkTypeAssignableTo(typePredicate.type, - getTypeOfNode(node.parameters[typePredicate.parameterIndex]), - typePredicateNode.type); - } + 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, + Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter); } - else if (typePredicateNode.parameterName) { - let hasReportedError = false; - for (var param of node.parameters) { - if (hasReportedError) { - break; - } - if (param.name.kind === SyntaxKind.ObjectBindingPattern || - param.name.kind === SyntaxKind.ArrayBindingPattern) { - - (function checkBindingPattern(pattern: BindingPattern) { - for (const element of pattern.elements) { - if (element.name.kind === SyntaxKind.Identifier && - (element.name).text === typePredicate.parameterName) { - - error(typePredicateNode.parameterName, - Diagnostics.A_type_predicate_cannot_reference_element_0_in_a_binding_pattern, - typePredicate.parameterName); - hasReportedError = true; - break; - } - else if (element.name.kind === SyntaxKind.ArrayBindingPattern || - element.name.kind === SyntaxKind.ObjectBindingPattern) { - - checkBindingPattern(element.name); - } - } - })(param.name); - } - } - if (!hasReportedError) { - error(typePredicateNode.parameterName, - Diagnostics.Cannot_find_parameter_0, - typePredicate.parameterName); - } + else { + checkTypeAssignableTo(typePredicate.type, + getTypeOfNode(node.parameters[typePredicate.parameterIndex]), + typePredicateNode.type); } } - else { - // Reuse this type diagnostics on the this type node to determine if a this type predicate is valid - getTypeFromThisTypeNode(typePredicateNode.parameterName as ThisTypeNode); + else if (typePredicateNode.parameterName) { + let hasReportedError = false; + for (var param of node.parameters) { + if (hasReportedError) { + break; + } + if (param.name.kind === SyntaxKind.ObjectBindingPattern || + param.name.kind === SyntaxKind.ArrayBindingPattern) { + + (function checkBindingPattern(pattern: BindingPattern) { + for (const element of pattern.elements) { + if (element.name.kind === SyntaxKind.Identifier && + (element.name).text === typePredicate.parameterName) { + + error(typePredicateNode.parameterName, + Diagnostics.A_type_predicate_cannot_reference_element_0_in_a_binding_pattern, + typePredicate.parameterName); + hasReportedError = true; + break; + } + else if (element.name.kind === SyntaxKind.ArrayBindingPattern || + element.name.kind === SyntaxKind.ObjectBindingPattern) { + + checkBindingPattern(element.name); + } + } + })(param.name); + } + } + if (!hasReportedError) { + error(typePredicateNode.parameterName, + Diagnostics.Cannot_find_parameter_0, + typePredicate.parameterName); + } } } else { - error(typePredicateNode, - Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); + 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 { @@ -14222,9 +14292,12 @@ namespace ts { } function checkTypePredicate(node: TypePredicateNode) { - if (!isInLegalTypePredicatePosition(node)) { + 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); + } } function checkSourceElement(node: Node): void { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 081bfc2385d..0ca8a93d071 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1636,6 +1636,10 @@ "category": "Error", "code": 2518 }, + "A 'this'-based type predicate is only allowed in class or interface members, get accessors, or return type positions for functions and methods": { + "category": "Error", + "code": 2519 + }, "Duplicate identifier '{0}'. Compiler uses declaration '{1}' to support async functions.": { "category": "Error", "code": 2520 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4bde87d0ff5..d8e6291ac02 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2108,6 +2108,7 @@ namespace ts { ESSymbol = 0x01000000, // Type of symbol primitive introduced in ES6 ThisType = 0x02000000, // This type ObjectLiteralPatternWithComputedProperties = 0x04000000, // Object literal type implied by binding pattern has computed properties + PredicateType = 0x08000000, /* @internal */ Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null, @@ -2140,6 +2141,11 @@ namespace ts { intrinsicName: string; // Name of intrinsic type } + // Predicate types (TypeFlags.Predicate) + export interface PredicateType extends Type { + predicate: ThisTypePredicate; + } + // String literal types (TypeFlags.StringLiteral) export interface StringLiteralType extends Type { text: string; // Text of string literal diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5a35bcbdee1..863fbcb23ef 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -693,7 +693,7 @@ namespace ts { } export function isIdentifierTypePredicate(predicate: TypePredicate): predicate is IdentifierTypePredicate { - return predicate.kind === TypePredicateKind.Identifier; + return predicate && predicate.kind === TypePredicateKind.Identifier; } export function getContainingFunction(node: Node): FunctionLikeDeclaration { diff --git a/tests/baselines/reference/typeGuardFunctionErrors.errors.txt b/tests/baselines/reference/typeGuardFunctionErrors.errors.txt index 92d9d21f0cc..9ad0ac6aa4a 100644 --- a/tests/baselines/reference/typeGuardFunctionErrors.errors.txt +++ b/tests/baselines/reference/typeGuardFunctionErrors.errors.txt @@ -25,7 +25,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(91,1): tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(96,9): error TS1228: A type predicate is only allowed in return type position for functions and methods. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(97,16): error TS1228: A type predicate is only allowed in return type position for functions and methods. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(98,20): error TS1228: A type predicate is only allowed in return type position for functions and methods. -tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(104,25): error TS1228: A type predicate is only allowed in return type position for functions and methods. +tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(104,25): error TS2519: A 'this'-based type predicate is only allowed in class or interface members, get accessors, or return type positions for functions and methods tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(105,16): error TS2322: Type 'boolean' is not assignable to type 'D'. Property 'm1' is missing in type 'Boolean'. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(105,16): error TS2409: Return type of constructor signature must be assignable to the instance type of the class @@ -194,7 +194,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(137,39 class D { constructor(p1: A): p1 is C { ~~~~~~~ -!!! error TS1228: A type predicate is only allowed in return type position for functions and methods. +!!! error TS2519: A 'this'-based type predicate is only allowed in class or interface members, get accessors, or return type positions for functions and methods return true; ~~~~ !!! error TS2322: Type 'boolean' is not assignable to type 'D'. diff --git a/tests/baselines/reference/typeGuardOfFormThisMember.js b/tests/baselines/reference/typeGuardOfFormThisMember.js new file mode 100644 index 00000000000..2a53be25ab1 --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFormThisMember.js @@ -0,0 +1,130 @@ +//// [typeGuardOfFormThisMember.ts] +// There's a 'File' class in the stdlib, wrap with a namespace to avoid collision +namespace Test { + export class FileSystemObject { + get isFile(): this is File { + return this instanceof File; + } + set isFile(param) { + // noop + } + get isDirectory(): this is Directory { + return this instanceof Directory; + } + isNetworked: this is (Networked & this); + constructor(public path: string) {} + } + + export class File extends FileSystemObject { + constructor(path: string, public content: string) { super(path); } + } + export class Directory extends FileSystemObject { + children: FileSystemObject[]; + } + export interface Networked { + host: string; + } + + let file: FileSystemObject = new File("foo/bar.txt", "foo"); + file.isNetworked = false; + file.isNetworked = file.isDirectory; + file.isFile = file.isNetworked; + let x = file.isFile; + if (file.isFile) { + file.content; + } + else if (file.isDirectory) { + file.children; + } + else if (file.isNetworked) { + file.host; + } +} + +//// [typeGuardOfFormThisMember.js] +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +// There's a 'File' class in the stdlib, wrap with a namespace to avoid collision +var Test; +(function (Test) { + var FileSystemObject = (function () { + function FileSystemObject(path) { + this.path = path; + } + Object.defineProperty(FileSystemObject.prototype, "isFile", { + get: function () { + return this instanceof File; + }, + set: function (param) { + // noop + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(FileSystemObject.prototype, "isDirectory", { + get: function () { + return this instanceof Directory; + }, + enumerable: true, + configurable: true + }); + return FileSystemObject; + })(); + Test.FileSystemObject = FileSystemObject; + var File = (function (_super) { + __extends(File, _super); + function File(path, content) { + _super.call(this, path); + this.content = content; + } + return File; + })(FileSystemObject); + Test.File = File; + var Directory = (function (_super) { + __extends(Directory, _super); + function Directory() { + _super.apply(this, arguments); + } + return Directory; + })(FileSystemObject); + Test.Directory = Directory; + var file = new File("foo/bar.txt", "foo"); + file.isNetworked = false; + file.isNetworked = file.isDirectory; + file.isFile = file.isNetworked; + var x = file.isFile; + if (file.isFile) { + file.content; + } + else if (file.isDirectory) { + file.children; + } + else if (file.isNetworked) { + file.host; + } +})(Test || (Test = {})); + + +//// [typeGuardOfFormThisMember.d.ts] +declare namespace Test { + class FileSystemObject { + path: string; + isFile: this is File; + isDirectory: this is Directory; + isNetworked: this is (Networked & this); + constructor(path: string); + } + class File extends FileSystemObject { + content: string; + constructor(path: string, content: string); + } + class Directory extends FileSystemObject { + children: FileSystemObject[]; + } + interface Networked { + host: string; + } +} diff --git a/tests/baselines/reference/typeGuardOfFormThisMember.symbols b/tests/baselines/reference/typeGuardOfFormThisMember.symbols new file mode 100644 index 00000000000..fe78ed16507 --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFormThisMember.symbols @@ -0,0 +1,126 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts === +// There's a 'File' class in the stdlib, wrap with a namespace to avoid collision +namespace Test { +>Test : Symbol(Test, Decl(typeGuardOfFormThisMember.ts, 0, 0)) + + export class FileSystemObject { +>FileSystemObject : Symbol(FileSystemObject, Decl(typeGuardOfFormThisMember.ts, 1, 16)) + + get isFile(): this is File { +>isFile : Symbol(isFile, Decl(typeGuardOfFormThisMember.ts, 2, 32), Decl(typeGuardOfFormThisMember.ts, 5, 3)) +>File : Symbol(File, Decl(typeGuardOfFormThisMember.ts, 14, 2)) + + return this instanceof File; +>this : Symbol(FileSystemObject, Decl(typeGuardOfFormThisMember.ts, 1, 16)) +>File : Symbol(File, Decl(typeGuardOfFormThisMember.ts, 14, 2)) + } + set isFile(param) { +>isFile : Symbol(isFile, Decl(typeGuardOfFormThisMember.ts, 2, 32), Decl(typeGuardOfFormThisMember.ts, 5, 3)) +>param : Symbol(param, Decl(typeGuardOfFormThisMember.ts, 6, 13)) + + // noop + } + get isDirectory(): this is Directory { +>isDirectory : Symbol(isDirectory, Decl(typeGuardOfFormThisMember.ts, 8, 3)) +>Directory : Symbol(Directory, Decl(typeGuardOfFormThisMember.ts, 18, 2)) + + return this instanceof Directory; +>this : Symbol(FileSystemObject, Decl(typeGuardOfFormThisMember.ts, 1, 16)) +>Directory : Symbol(Directory, Decl(typeGuardOfFormThisMember.ts, 18, 2)) + } + isNetworked: this is (Networked & this); +>isNetworked : Symbol(isNetworked, Decl(typeGuardOfFormThisMember.ts, 11, 3)) +>Networked : Symbol(Networked, Decl(typeGuardOfFormThisMember.ts, 21, 2)) + + constructor(public path: string) {} +>path : Symbol(path, Decl(typeGuardOfFormThisMember.ts, 13, 14)) + } + + export class File extends FileSystemObject { +>File : Symbol(File, Decl(typeGuardOfFormThisMember.ts, 14, 2)) +>FileSystemObject : Symbol(FileSystemObject, Decl(typeGuardOfFormThisMember.ts, 1, 16)) + + constructor(path: string, public content: string) { super(path); } +>path : Symbol(path, Decl(typeGuardOfFormThisMember.ts, 17, 14)) +>content : Symbol(content, Decl(typeGuardOfFormThisMember.ts, 17, 27)) +>super : Symbol(FileSystemObject, Decl(typeGuardOfFormThisMember.ts, 1, 16)) +>path : Symbol(path, Decl(typeGuardOfFormThisMember.ts, 17, 14)) + } + export class Directory extends FileSystemObject { +>Directory : Symbol(Directory, Decl(typeGuardOfFormThisMember.ts, 18, 2)) +>FileSystemObject : Symbol(FileSystemObject, Decl(typeGuardOfFormThisMember.ts, 1, 16)) + + children: FileSystemObject[]; +>children : Symbol(children, Decl(typeGuardOfFormThisMember.ts, 19, 50)) +>FileSystemObject : Symbol(FileSystemObject, Decl(typeGuardOfFormThisMember.ts, 1, 16)) + } + export interface Networked { +>Networked : Symbol(Networked, Decl(typeGuardOfFormThisMember.ts, 21, 2)) + + host: string; +>host : Symbol(host, Decl(typeGuardOfFormThisMember.ts, 22, 29)) + } + + let file: FileSystemObject = new File("foo/bar.txt", "foo"); +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>FileSystemObject : Symbol(FileSystemObject, Decl(typeGuardOfFormThisMember.ts, 1, 16)) +>File : Symbol(File, Decl(typeGuardOfFormThisMember.ts, 14, 2)) + + file.isNetworked = false; +>file.isNetworked : Symbol(FileSystemObject.isNetworked, Decl(typeGuardOfFormThisMember.ts, 11, 3)) +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>isNetworked : Symbol(FileSystemObject.isNetworked, Decl(typeGuardOfFormThisMember.ts, 11, 3)) + + file.isNetworked = file.isDirectory; +>file.isNetworked : Symbol(FileSystemObject.isNetworked, Decl(typeGuardOfFormThisMember.ts, 11, 3)) +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>isNetworked : Symbol(FileSystemObject.isNetworked, Decl(typeGuardOfFormThisMember.ts, 11, 3)) +>file.isDirectory : Symbol(FileSystemObject.isDirectory, Decl(typeGuardOfFormThisMember.ts, 8, 3)) +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>isDirectory : Symbol(FileSystemObject.isDirectory, Decl(typeGuardOfFormThisMember.ts, 8, 3)) + + file.isFile = file.isNetworked; +>file.isFile : Symbol(FileSystemObject.isFile, Decl(typeGuardOfFormThisMember.ts, 2, 32), Decl(typeGuardOfFormThisMember.ts, 5, 3)) +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>isFile : Symbol(FileSystemObject.isFile, Decl(typeGuardOfFormThisMember.ts, 2, 32), Decl(typeGuardOfFormThisMember.ts, 5, 3)) +>file.isNetworked : Symbol(FileSystemObject.isNetworked, Decl(typeGuardOfFormThisMember.ts, 11, 3)) +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>isNetworked : Symbol(FileSystemObject.isNetworked, Decl(typeGuardOfFormThisMember.ts, 11, 3)) + + let x = file.isFile; +>x : Symbol(x, Decl(typeGuardOfFormThisMember.ts, 30, 4)) +>file.isFile : Symbol(FileSystemObject.isFile, Decl(typeGuardOfFormThisMember.ts, 2, 32), Decl(typeGuardOfFormThisMember.ts, 5, 3)) +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>isFile : Symbol(FileSystemObject.isFile, Decl(typeGuardOfFormThisMember.ts, 2, 32), Decl(typeGuardOfFormThisMember.ts, 5, 3)) + + if (file.isFile) { +>file.isFile : Symbol(FileSystemObject.isFile, Decl(typeGuardOfFormThisMember.ts, 2, 32), Decl(typeGuardOfFormThisMember.ts, 5, 3)) +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>isFile : Symbol(FileSystemObject.isFile, Decl(typeGuardOfFormThisMember.ts, 2, 32), Decl(typeGuardOfFormThisMember.ts, 5, 3)) + + file.content; +>file.content : Symbol(File.content, Decl(typeGuardOfFormThisMember.ts, 17, 27)) +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>content : Symbol(File.content, Decl(typeGuardOfFormThisMember.ts, 17, 27)) + } + else if (file.isDirectory) { +>file.isDirectory : Symbol(FileSystemObject.isDirectory, Decl(typeGuardOfFormThisMember.ts, 8, 3)) +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>isDirectory : Symbol(FileSystemObject.isDirectory, Decl(typeGuardOfFormThisMember.ts, 8, 3)) + + file.children; +>file.children : Symbol(Directory.children, Decl(typeGuardOfFormThisMember.ts, 19, 50)) +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>children : Symbol(Directory.children, Decl(typeGuardOfFormThisMember.ts, 19, 50)) + } + else if (file.isNetworked) { +>file.isNetworked : Symbol(FileSystemObject.isNetworked, Decl(typeGuardOfFormThisMember.ts, 11, 3)) +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>isNetworked : Symbol(FileSystemObject.isNetworked, Decl(typeGuardOfFormThisMember.ts, 11, 3)) + + file.host; +>file.host : Symbol(Networked.host, Decl(typeGuardOfFormThisMember.ts, 22, 29)) +>file : Symbol(file, Decl(typeGuardOfFormThisMember.ts, 26, 4)) +>host : Symbol(Networked.host, Decl(typeGuardOfFormThisMember.ts, 22, 29)) + } +} diff --git a/tests/baselines/reference/typeGuardOfFormThisMember.types b/tests/baselines/reference/typeGuardOfFormThisMember.types new file mode 100644 index 00000000000..c379186fac7 --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFormThisMember.types @@ -0,0 +1,136 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts === +// There's a 'File' class in the stdlib, wrap with a namespace to avoid collision +namespace Test { +>Test : typeof Test + + export class FileSystemObject { +>FileSystemObject : FileSystemObject + + get isFile(): this is File { +>isFile : this is File +>File : File + + return this instanceof File; +>this instanceof File : boolean +>this : this +>File : typeof File + } + set isFile(param) { +>isFile : this is File +>param : this is File + + // noop + } + get isDirectory(): this is Directory { +>isDirectory : this is Directory +>Directory : Directory + + return this instanceof Directory; +>this instanceof Directory : boolean +>this : this +>Directory : typeof Directory + } + isNetworked: this is (Networked & this); +>isNetworked : this is Networked & this +>Networked : Networked + + constructor(public path: string) {} +>path : string + } + + export class File extends FileSystemObject { +>File : File +>FileSystemObject : FileSystemObject + + constructor(path: string, public content: string) { super(path); } +>path : string +>content : string +>super(path) : void +>super : typeof FileSystemObject +>path : string + } + export class Directory extends FileSystemObject { +>Directory : Directory +>FileSystemObject : FileSystemObject + + children: FileSystemObject[]; +>children : FileSystemObject[] +>FileSystemObject : FileSystemObject + } + export interface Networked { +>Networked : Networked + + host: string; +>host : string + } + + let file: FileSystemObject = new File("foo/bar.txt", "foo"); +>file : FileSystemObject +>FileSystemObject : FileSystemObject +>new File("foo/bar.txt", "foo") : File +>File : typeof File +>"foo/bar.txt" : string +>"foo" : string + + file.isNetworked = false; +>file.isNetworked = false : boolean +>file.isNetworked : this is Networked & this +>file : FileSystemObject +>isNetworked : this is Networked & this +>false : boolean + + file.isNetworked = file.isDirectory; +>file.isNetworked = file.isDirectory : this is Directory +>file.isNetworked : this is Networked & this +>file : FileSystemObject +>isNetworked : this is Networked & this +>file.isDirectory : this is Directory +>file : FileSystemObject +>isDirectory : this is Directory + + file.isFile = file.isNetworked; +>file.isFile = file.isNetworked : this is Networked & this +>file.isFile : this is File +>file : FileSystemObject +>isFile : this is File +>file.isNetworked : this is Networked & this +>file : FileSystemObject +>isNetworked : this is Networked & this + + let x = file.isFile; +>x : this is File +>file.isFile : this is File +>file : FileSystemObject +>isFile : this is File + + if (file.isFile) { +>file.isFile : this is File +>file : FileSystemObject +>isFile : this is File + + file.content; +>file.content : string +>file : File +>content : string + } + else if (file.isDirectory) { +>file.isDirectory : this is Directory +>file : FileSystemObject +>isDirectory : this is Directory + + file.children; +>file.children : FileSystemObject[] +>file : Directory +>children : FileSystemObject[] + } + else if (file.isNetworked) { +>file.isNetworked : this is Networked & this +>file : FileSystemObject +>isNetworked : this is Networked & this + + file.host; +>file.host : string +>file : Networked & this +>host : string + } +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts new file mode 100644 index 00000000000..1c714ff7d5a --- /dev/null +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFormThisMember.ts @@ -0,0 +1,43 @@ +// @target: es5 +// @declaration: true +// There's a 'File' class in the stdlib, wrap with a namespace to avoid collision +namespace Test { + export class FileSystemObject { + get isFile(): this is File { + return this instanceof File; + } + set isFile(param) { + // noop + } + get isDirectory(): this is Directory { + return this instanceof Directory; + } + isNetworked: this is (Networked & this); + constructor(public path: string) {} + } + + export class File extends FileSystemObject { + constructor(path: string, public content: string) { super(path); } + } + export class Directory extends FileSystemObject { + children: FileSystemObject[]; + } + export interface Networked { + host: string; + } + + let file: FileSystemObject = new File("foo/bar.txt", "foo"); + file.isNetworked = false; + file.isNetworked = file.isDirectory; + file.isFile = file.isNetworked; + let x = file.isFile; + if (file.isFile) { + file.content; + } + else if (file.isDirectory) { + file.children; + } + else if (file.isNetworked) { + file.host; + } +} \ No newline at end of file