Skip to content

Commit 58400ed

Browse files
committed
Merge pull request #5906 from weswigham/this-type-guards
This type predicates for type guards
2 parents 6e06752 + 8e58694 commit 58400ed

30 files changed

+2942
-177
lines changed

src/compiler/binder.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1189,7 +1189,8 @@ namespace ts {
11891189
case SyntaxKind.ThisType:
11901190
seenThisKeyword = true;
11911191
return;
1192-
1192+
case SyntaxKind.TypePredicate:
1193+
return checkTypePredicate(node as TypePredicateNode);
11931194
case SyntaxKind.TypeParameter:
11941195
return declareSymbolAndAddToSymbolTable(<Declaration>node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
11951196
case SyntaxKind.Parameter:
@@ -1275,6 +1276,17 @@ namespace ts {
12751276
}
12761277
}
12771278

1279+
function checkTypePredicate(node: TypePredicateNode) {
1280+
const { parameterName, type } = node;
1281+
if (parameterName && parameterName.kind === SyntaxKind.Identifier) {
1282+
checkStrictModeIdentifier(parameterName as Identifier);
1283+
}
1284+
if (parameterName && parameterName.kind === SyntaxKind.ThisType) {
1285+
seenThisKeyword = true;
1286+
}
1287+
bind(type);
1288+
}
1289+
12781290
function bindSourceFileIfExternalModule() {
12791291
setExportContextFlag(file);
12801292
if (isExternalModule(file)) {

src/compiler/checker.ts

Lines changed: 240 additions & 120 deletions
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,6 +1647,14 @@
16471647
"category": "Error",
16481648
"code": 2517
16491649
},
1650+
"A 'this'-based type guard is not compatible with a parameter-based type guard.": {
1651+
"category": "Error",
1652+
"code": 2518
1653+
},
1654+
"A 'this'-based type predicate is only allowed within a class or interface's members, get accessors, or return type positions for functions and methods.": {
1655+
"category": "Error",
1656+
"code": 2519
1657+
},
16501658
"Duplicate identifier '{0}'. Compiler uses declaration '{1}' to support async functions.": {
16511659
"category": "Error",
16521660
"code": 2520

src/compiler/parser.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1963,11 +1963,7 @@ namespace ts {
19631963
function parseTypeReferenceOrTypePredicate(): TypeReferenceNode | TypePredicateNode {
19641964
const typeName = parseEntityName(/*allowReservedWords*/ false, Diagnostics.Type_expected);
19651965
if (typeName.kind === SyntaxKind.Identifier && token === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) {
1966-
nextToken();
1967-
const node = <TypePredicateNode>createNode(SyntaxKind.TypePredicate, typeName.pos);
1968-
node.parameterName = <Identifier>typeName;
1969-
node.type = parseType();
1970-
return finishNode(node);
1966+
return parseTypePredicate(typeName as Identifier);
19711967
}
19721968
const node = <TypeReferenceNode>createNode(SyntaxKind.TypeReference, typeName.pos);
19731969
node.typeName = typeName;
@@ -1977,8 +1973,16 @@ namespace ts {
19771973
return finishNode(node);
19781974
}
19791975

1980-
function parseThisTypeNode(): TypeNode {
1981-
const node = <TypeNode>createNode(SyntaxKind.ThisType);
1976+
function parseTypePredicate(lhs: Identifier | ThisTypeNode): TypePredicateNode {
1977+
nextToken();
1978+
const node = createNode(SyntaxKind.TypePredicate, lhs.pos) as TypePredicateNode;
1979+
node.parameterName = lhs;
1980+
node.type = parseType();
1981+
return finishNode(node);
1982+
}
1983+
1984+
function parseThisTypeNode(): ThisTypeNode {
1985+
const node = createNode(SyntaxKind.ThisType) as ThisTypeNode;
19821986
nextToken();
19831987
return finishNode(node);
19841988
}
@@ -2424,8 +2428,15 @@ namespace ts {
24242428
return parseStringLiteralTypeNode();
24252429
case SyntaxKind.VoidKeyword:
24262430
return parseTokenNode<TypeNode>();
2427-
case SyntaxKind.ThisKeyword:
2428-
return parseThisTypeNode();
2431+
case SyntaxKind.ThisKeyword: {
2432+
const thisKeyword = parseThisTypeNode();
2433+
if (token === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) {
2434+
return parseTypePredicate(thisKeyword);
2435+
}
2436+
else {
2437+
return thisKeyword;
2438+
}
2439+
}
24292440
case SyntaxKind.TypeOfKeyword:
24302441
return parseTypeQuery();
24312442
case SyntaxKind.OpenBraceToken:

src/compiler/types.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -733,11 +733,15 @@ namespace ts {
733733
// @kind(SyntaxKind.StringKeyword)
734734
// @kind(SyntaxKind.SymbolKeyword)
735735
// @kind(SyntaxKind.VoidKeyword)
736-
// @kind(SyntaxKind.ThisType)
737736
export interface TypeNode extends Node {
738737
_typeNodeBrand: any;
739738
}
740739

740+
// @kind(SyntaxKind.ThisType)
741+
export interface ThisTypeNode extends TypeNode {
742+
_thisTypeNodeBrand: any;
743+
}
744+
741745
export interface FunctionOrConstructorTypeNode extends TypeNode, SignatureDeclaration {
742746
_functionOrConstructorTypeNodeBrand: any;
743747
}
@@ -756,7 +760,7 @@ namespace ts {
756760

757761
// @kind(SyntaxKind.TypePredicate)
758762
export interface TypePredicateNode extends TypeNode {
759-
parameterName: Identifier;
763+
parameterName: Identifier | ThisTypeNode;
760764
type: TypeNode;
761765
}
762766

@@ -1820,10 +1824,25 @@ namespace ts {
18201824
CannotBeNamed
18211825
}
18221826

1827+
export const enum TypePredicateKind {
1828+
This,
1829+
Identifier
1830+
}
1831+
18231832
export interface TypePredicate {
1833+
kind: TypePredicateKind;
1834+
type: Type;
1835+
}
1836+
1837+
// @kind (TypePredicateKind.This)
1838+
export interface ThisTypePredicate extends TypePredicate {
1839+
_thisTypePredicateBrand: any;
1840+
}
1841+
1842+
// @kind (TypePredicateKind.Identifier)
1843+
export interface IdentifierTypePredicate extends TypePredicate {
18241844
parameterName: string;
18251845
parameterIndex: number;
1826-
type: Type;
18271846
}
18281847

18291848
/* @internal */
@@ -2091,6 +2110,7 @@ namespace ts {
20912110
ESSymbol = 0x01000000, // Type of symbol primitive introduced in ES6
20922111
ThisType = 0x02000000, // This type
20932112
ObjectLiteralPatternWithComputedProperties = 0x04000000, // Object literal type implied by binding pattern has computed properties
2113+
PredicateType = 0x08000000, // Predicate types are also Boolean types, but should not be considered Intrinsics - there's no way to capture this with flags
20942114

20952115
/* @internal */
20962116
Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null,
@@ -2102,7 +2122,7 @@ namespace ts {
21022122
UnionOrIntersection = Union | Intersection,
21032123
StructuredType = ObjectType | Union | Intersection,
21042124
/* @internal */
2105-
RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral,
2125+
RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral | PredicateType,
21062126
/* @internal */
21072127
PropagatingFlags = ContainsUndefinedOrNull | ContainsObjectLiteral | ContainsAnyFunctionType
21082128
}
@@ -2123,6 +2143,11 @@ namespace ts {
21232143
intrinsicName: string; // Name of intrinsic type
21242144
}
21252145

2146+
// Predicate types (TypeFlags.Predicate)
2147+
export interface PredicateType extends Type {
2148+
predicate: ThisTypePredicate | IdentifierTypePredicate;
2149+
}
2150+
21262151
// String literal types (TypeFlags.StringLiteral)
21272152
export interface StringLiteralType extends Type {
21282153
text: string; // Text of string literal
@@ -2239,7 +2264,6 @@ namespace ts {
22392264
declaration: SignatureDeclaration; // Originating declaration
22402265
typeParameters: TypeParameter[]; // Type parameters (undefined if non-generic)
22412266
parameters: Symbol[]; // Parameters
2242-
typePredicate?: TypePredicate; // Type predicate
22432267
/* @internal */
22442268
resolvedReturnType: Type; // Resolved return type
22452269
/* @internal */

src/compiler/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,10 @@ namespace ts {
693693
return node && node.kind === SyntaxKind.MethodDeclaration && node.parent.kind === SyntaxKind.ObjectLiteralExpression;
694694
}
695695

696+
export function isIdentifierTypePredicate(predicate: TypePredicate): predicate is IdentifierTypePredicate {
697+
return predicate && predicate.kind === TypePredicateKind.Identifier;
698+
}
699+
696700
export function getContainingFunction(node: Node): FunctionLikeDeclaration {
697701
while (true) {
698702
node = node.parent;

tests/baselines/reference/arrayBufferIsViewNarrowsType.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ var obj: Object;
44
>Object : Object
55

66
if (ArrayBuffer.isView(obj)) {
7-
>ArrayBuffer.isView(obj) : boolean
7+
>ArrayBuffer.isView(obj) : arg is ArrayBufferView
88
>ArrayBuffer.isView : (arg: any) => arg is ArrayBufferView
99
>ArrayBuffer : ArrayBufferConstructor
1010
>isView : (arg: any) => arg is ArrayBufferView

tests/baselines/reference/isArray.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ var maybeArray: number | number[];
44

55

66
if (Array.isArray(maybeArray)) {
7-
>Array.isArray(maybeArray) : boolean
7+
>Array.isArray(maybeArray) : arg is any[]
88
>Array.isArray : (arg: any) => arg is any[]
99
>Array : ArrayConstructor
1010
>isArray : (arg: any) => arg is any[]

tests/baselines/reference/stringLiteralCheckedInIf02.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function f(foo: T) {
3131
>T : ("a" | "b")[] | "a" | "b"
3232

3333
if (isS(foo)) {
34-
>isS(foo) : boolean
34+
>isS(foo) : t is "a" | "b"
3535
>isS : (t: ("a" | "b")[] | "a" | "b") => t is "a" | "b"
3636
>foo : ("a" | "b")[] | "a" | "b"
3737

tests/baselines/reference/stringLiteralTypesAsTags01.errors.txt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts(18,10): error TS2382: Specialized overload signature is not assignable to any non-specialized signature.
2-
tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts(19,10): error TS2382: Specialized overload signature is not assignable to any non-specialized signature.
31
tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts(20,10): error TS2394: Overload signature is not compatible with function implementation.
42
tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts(22,21): error TS2304: Cannot find name 'is'.
53

64

7-
==== tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts (4 errors) ====
5+
==== tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts (2 errors) ====
86

97
type Kind = "A" | "B"
108

@@ -23,11 +21,7 @@ tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts(22,21)
2321
}
2422

2523
function hasKind(entity: Entity, kind: "A"): entity is A;
26-
~~~~~~~
27-
!!! error TS2382: Specialized overload signature is not assignable to any non-specialized signature.
2824
function hasKind(entity: Entity, kind: "B"): entity is B;
29-
~~~~~~~
30-
!!! error TS2382: Specialized overload signature is not assignable to any non-specialized signature.
3125
function hasKind(entity: Entity, kind: Kind): entity is Entity;
3226
~~~~~~~
3327
!!! error TS2394: Overload signature is not compatible with function implementation.

0 commit comments

Comments
 (0)