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

+13-1
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

+240-120
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

+8
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

+20-9
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

+29-5
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

+4
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

+1-1
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

+1-1
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

+1-1
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

+1-7
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.

tests/baselines/reference/typeGuardFunction.types

+7-7
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ var b: B;
5454

5555
// Basic
5656
if (isC(a)) {
57-
>isC(a) : boolean
57+
>isC(a) : p1 is C
5858
>isC : (p1: any) => p1 is C
5959
>a : A
6060

@@ -70,7 +70,7 @@ var subType: C;
7070
>C : C
7171

7272
if(isA(subType)) {
73-
>isA(subType) : boolean
73+
>isA(subType) : p1 is A
7474
>isA : (p1: any) => p1 is A
7575
>subType : C
7676

@@ -87,7 +87,7 @@ var union: A | B;
8787
>B : B
8888

8989
if(isA(union)) {
90-
>isA(union) : boolean
90+
>isA(union) : p1 is A
9191
>isA : (p1: any) => p1 is A
9292
>union : A | B
9393

@@ -118,7 +118,7 @@ declare function isC_multipleParams(p1, p2): p1 is C;
118118
>C : C
119119

120120
if (isC_multipleParams(a, 0)) {
121-
>isC_multipleParams(a, 0) : boolean
121+
>isC_multipleParams(a, 0) : p1 is C
122122
>isC_multipleParams : (p1: any, p2: any) => p1 is C
123123
>a : A
124124
>0 : number
@@ -197,7 +197,7 @@ declare function acceptingBoolean(a: boolean);
197197
acceptingBoolean(isA(a));
198198
>acceptingBoolean(isA(a)) : any
199199
>acceptingBoolean : (a: boolean) => any
200-
>isA(a) : boolean
200+
>isA(a) : p1 is A
201201
>isA : (p1: any) => p1 is A
202202
>a : A
203203

@@ -223,8 +223,8 @@ let union2: C | B;
223223
let union3: boolean | B = isA(union2) || union2;
224224
>union3 : boolean | B
225225
>B : B
226-
>isA(union2) || union2 : boolean | B
227-
>isA(union2) : boolean
226+
>isA(union2) || union2 : p1 is A | B
227+
>isA(union2) : p1 is A
228228
>isA : (p1: any) => p1 is A
229229
>union2 : C | B
230230
>union2 : B

tests/baselines/reference/typeGuardFunctionErrors.errors.txt

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(15,12): error TS2322: Type 'string' is not assignable to type 'boolean'.
1+
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(15,12): error TS2322: Type 'string' is not assignable to type 'x is A'.
22
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(22,33): error TS2304: Cannot find name 'x'.
33
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(26,33): error TS1225: Cannot find parameter 'x'.
44
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(30,10): error TS2391: Function implementation is missing or not immediately following the declaration.
@@ -16,6 +16,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(70,7):
1616
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(75,46): error TS2345: Argument of type '(p1: any) => p1 is C' is not assignable to parameter of type '(p1: any) => p1 is B'.
1717
Type predicate 'p1 is C' is not assignable to 'p1 is B'.
1818
Type 'C' is not assignable to type 'B'.
19+
Property 'propB' is missing in type 'C'.
1920
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(79,1): error TS2322: Type '(p1: any, p2: any) => boolean' is not assignable to type '(p1: any, p2: any) => p1 is A'.
2021
Signature '(p1: any, p2: any): boolean' must have a type predicate.
2122
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(85,1): error TS2322: Type '(p1: any, p2: any) => p2 is A' is not assignable to type '(p1: any, p2: any) => p1 is A'.
@@ -25,14 +26,14 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(91,1):
2526
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.
2627
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.
2728
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.
28-
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.
2929
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(105,16): error TS2322: Type 'boolean' is not assignable to type 'D'.
3030
Property 'm1' is missing in type 'Boolean'.
3131
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
3232
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(107,20): error TS1228: A type predicate is only allowed in return type position for functions and methods.
3333
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(110,20): error TS1228: A type predicate is only allowed in return type position for functions and methods.
3434
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(111,16): error TS2408: Setters cannot return a value.
3535
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(116,18): error TS1228: A type predicate is only allowed in return type position for functions and methods.
36+
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(120,22): error TS1225: Cannot find parameter 'p1'.
3637
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(120,22): error TS1228: A type predicate is only allowed in return type position for functions and methods.
3738
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(124,20): error TS1229: A type predicate cannot reference a rest parameter.
3839
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(129,34): error TS1230: A type predicate cannot reference element 'p1' in a binding pattern.
@@ -57,7 +58,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(137,39
5758
function hasANonBooleanReturnStatement(x): x is A {
5859
return '';
5960
~~
60-
!!! error TS2322: Type 'string' is not assignable to type 'boolean'.
61+
!!! error TS2322: Type 'string' is not assignable to type 'x is A'.
6162
}
6263

6364
function hasTypeGuardTypeInsideTypeGuardType(x): x is x is A {
@@ -149,6 +150,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(137,39
149150
!!! error TS2345: Argument of type '(p1: any) => p1 is C' is not assignable to parameter of type '(p1: any) => p1 is B'.
150151
!!! error TS2345: Type predicate 'p1 is C' is not assignable to 'p1 is B'.
151152
!!! error TS2345: Type 'C' is not assignable to type 'B'.
153+
!!! error TS2345: Property 'propB' is missing in type 'C'.
152154

153155
// Boolean not assignable to type guard
154156
var assign1: (p1, p2) => p1 is A;
@@ -193,8 +195,6 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(137,39
193195
// Non-compatiable type predicate positions for signature declarations
194196
class D {
195197
constructor(p1: A): p1 is C {
196-
~~~~~~~
197-
!!! error TS1228: A type predicate is only allowed in return type position for functions and methods.
198198
return true;
199199
~~~~
200200
!!! error TS2322: Type 'boolean' is not assignable to type 'D'.
@@ -224,6 +224,8 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(137,39
224224

225225
interface I2 {
226226
[index: number]: p1 is C;
227+
~~
228+
!!! error TS1225: Cannot find parameter 'p1'.
227229
~~~~~~~
228230
!!! error TS1228: A type predicate is only allowed in return type position for functions and methods.
229231
}

tests/baselines/reference/typeGuardFunctionGenerics.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ let test1: boolean = funA(isB);
100100
>isB : (p1: any) => p1 is B
101101

102102
if (funB(retC, a)) {
103-
>funB(retC, a) : boolean
103+
>funB(retC, a) : p2 is C
104104
>funB : <T>(p1: (p1: any) => T, p2: any) => p2 is T
105105
>retC : (x: any) => C
106106
>a : A
@@ -118,7 +118,7 @@ let test2: B = funC(isB);
118118
>isB : (p1: any) => p1 is B
119119

120120
if (funD(isC, a)) {
121-
>funD(isC, a) : boolean
121+
>funD(isC, a) : p2 is C
122122
>funD : <T>(p1: (p1: any) => p1 is T, p2: any) => p2 is T
123123
>isC : (p1: any) => p1 is C
124124
>a : A

0 commit comments

Comments
 (0)