Skip to content

Commit 6e69a9e

Browse files
committed
Merge pull request #3262 from tinganho/customTypeGuard2
Custom type guard function
2 parents 08cf559 + efb7013 commit 6e69a9e

27 files changed

+2842
-41
lines changed

bin/tsc

100644100755
File mode changed.

src/compiler/checker.ts

+219-23
Large diffs are not rendered by default.

src/compiler/diagnosticInformationMap.generated.ts

+7
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,13 @@ module ts {
179179
Generators_are_not_allowed_in_an_ambient_context: { code: 1221, category: DiagnosticCategory.Error, key: "Generators are not allowed in an ambient context." },
180180
An_overload_signature_cannot_be_declared_as_a_generator: { code: 1222, category: DiagnosticCategory.Error, key: "An overload signature cannot be declared as a generator." },
181181
_0_tag_already_specified: { code: 1223, category: DiagnosticCategory.Error, key: "'{0}' tag already specified." },
182+
Signature_0_must_have_a_type_predicate: { code: 1224, category: DiagnosticCategory.Error, key: "Signature '{0}' must have a type predicate." },
183+
Cannot_find_parameter_0: { code: 1225, category: DiagnosticCategory.Error, key: "Cannot find parameter '{0}'." },
184+
Type_predicate_0_is_not_assignable_to_1: { code: 1226, category: DiagnosticCategory.Error, key: "Type predicate '{0}' is not assignable to '{1}'." },
185+
Parameter_0_is_not_in_the_same_position_as_parameter_1: { code: 1227, category: DiagnosticCategory.Error, key: "Parameter '{0}' is not in the same position as parameter '{1}'." },
186+
A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods: { code: 1228, category: DiagnosticCategory.Error, key: "A type predicate is only allowed in return type position for functions and methods." },
187+
A_type_predicate_cannot_reference_a_rest_parameter: { code: 1229, category: DiagnosticCategory.Error, key: "A type predicate cannot reference a rest parameter." },
188+
A_type_predicate_cannot_reference_element_0_in_a_binding_pattern: { code: 1230, category: DiagnosticCategory.Error, key: "A type predicate cannot reference element '{0}' in a binding pattern." },
182189
Duplicate_identifier_0: { code: 2300, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." },
183190
Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor: { code: 2301, category: DiagnosticCategory.Error, key: "Initializer of instance member variable '{0}' cannot reference identifier '{1}' declared in the constructor." },
184191
Static_members_cannot_reference_class_type_parameters: { code: 2302, category: DiagnosticCategory.Error, key: "Static members cannot reference class type parameters." },

src/compiler/diagnosticMessages.json

+29
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,35 @@
703703
"category": "Error",
704704
"code": 1223
705705
},
706+
"Signature '{0}' must have a type predicate.": {
707+
"category": "Error",
708+
"code": 1224
709+
},
710+
"Cannot find parameter '{0}'.": {
711+
"category": "Error",
712+
"code": 1225
713+
},
714+
"Type predicate '{0}' is not assignable to '{1}'.": {
715+
"category": "Error",
716+
"code": 1226
717+
},
718+
"Parameter '{0}' is not in the same position as parameter '{1}'.": {
719+
"category": "Error",
720+
"code": 1227
721+
},
722+
"A type predicate is only allowed in return type position for functions and methods.": {
723+
"category": "Error",
724+
"code": 1228
725+
},
726+
"A type predicate cannot reference a rest parameter.": {
727+
"category": "Error",
728+
"code": 1229
729+
},
730+
"A type predicate cannot reference element '{0}' in a binding pattern.": {
731+
"category": "Error",
732+
"code": 1230
733+
},
734+
706735

707736
"Duplicate identifier '{0}'.": {
708737
"category": "Error",

src/compiler/parser.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ module ts {
103103
case SyntaxKind.TypeReference:
104104
return visitNode(cbNode, (<TypeReferenceNode>node).typeName) ||
105105
visitNodes(cbNodes, (<TypeReferenceNode>node).typeArguments);
106+
case SyntaxKind.TypePredicate:
107+
return visitNode(cbNode, (<TypePredicateNode>node).parameterName) ||
108+
visitNode(cbNode, (<TypePredicateNode>node).type);
106109
case SyntaxKind.TypeQuery:
107110
return visitNode(cbNode, (<TypeQueryNode>node).exprName);
108111
case SyntaxKind.TypeLiteral:
@@ -1897,9 +1900,17 @@ module ts {
18971900

18981901
// TYPES
18991902

1900-
function parseTypeReference(): TypeReferenceNode {
1901-
let node = <TypeReferenceNode>createNode(SyntaxKind.TypeReference);
1902-
node.typeName = parseEntityName(/*allowReservedWords*/ false, Diagnostics.Type_expected);
1903+
function parseTypeReferenceOrTypePredicate(): TypeReferenceNode | TypePredicateNode {
1904+
let typeName = parseEntityName(/*allowReservedWords*/ false, Diagnostics.Type_expected);
1905+
if (typeName.kind === SyntaxKind.Identifier && token === SyntaxKind.IsKeyword) {
1906+
nextToken();
1907+
let node = <TypePredicateNode>createNode(SyntaxKind.TypePredicate, typeName.pos);
1908+
node.parameterName = <Identifier>typeName;
1909+
node.type = parseType();
1910+
return finishNode(node);
1911+
}
1912+
let node = <TypeReferenceNode>createNode(SyntaxKind.TypeReference, typeName.pos);
1913+
node.typeName = typeName;
19031914
if (!scanner.hasPrecedingLineBreak() && token === SyntaxKind.LessThanToken) {
19041915
node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken);
19051916
}
@@ -2336,7 +2347,7 @@ module ts {
23362347
case SyntaxKind.SymbolKeyword:
23372348
// If these are followed by a dot, then parse these out as a dotted type reference instead.
23382349
let node = tryParse(parseKeywordAndNoDot);
2339-
return node || parseTypeReference();
2350+
return node || parseTypeReferenceOrTypePredicate();
23402351
case SyntaxKind.VoidKeyword:
23412352
return parseTokenNode<TypeNode>();
23422353
case SyntaxKind.TypeOfKeyword:
@@ -2348,7 +2359,7 @@ module ts {
23482359
case SyntaxKind.OpenParenToken:
23492360
return parseParenthesizedType();
23502361
default:
2351-
return parseTypeReference();
2362+
return parseTypeReferenceOrTypePredicate();
23522363
}
23532364
}
23542365

src/compiler/scanner.ts

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ module ts {
7272
"in": SyntaxKind.InKeyword,
7373
"instanceof": SyntaxKind.InstanceOfKeyword,
7474
"interface": SyntaxKind.InterfaceKeyword,
75+
"is": SyntaxKind.IsKeyword,
7576
"let": SyntaxKind.LetKeyword,
7677
"module": SyntaxKind.ModuleKeyword,
7778
"namespace": SyntaxKind.NamespaceKeyword,

src/compiler/types.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ module ts {
145145
ConstructorKeyword,
146146
DeclareKeyword,
147147
GetKeyword,
148+
IsKeyword,
148149
ModuleKeyword,
149150
NamespaceKeyword,
150151
RequireKeyword,
@@ -177,6 +178,7 @@ module ts {
177178
ConstructSignature,
178179
IndexSignature,
179180
// Type
181+
TypePredicate,
180182
TypeReference,
181183
FunctionType,
182184
ConstructorType,
@@ -614,6 +616,11 @@ module ts {
614616
typeArguments?: NodeArray<TypeNode>;
615617
}
616618

619+
export interface TypePredicateNode extends TypeNode {
620+
parameterName: Identifier;
621+
type: TypeNode;
622+
}
623+
617624
export interface TypeQueryNode extends TypeNode {
618625
exprName: EntityName;
619626
}
@@ -1386,6 +1393,12 @@ module ts {
13861393
NotAccessible,
13871394
CannotBeNamed
13881395
}
1396+
1397+
export interface TypePredicate {
1398+
parameterName: string;
1399+
parameterIndex: number;
1400+
type: Type;
1401+
}
13891402

13901403
/* @internal */
13911404
export type AnyImportSyntax = ImportDeclaration | ImportEqualsDeclaration;
@@ -1593,12 +1606,12 @@ module ts {
15931606
Union = 0x00004000, // Union
15941607
Anonymous = 0x00008000, // Anonymous
15951608
Instantiated = 0x00010000, // Instantiated anonymous type
1596-
/* @internal */
1609+
/* @internal */
15971610
FromSignature = 0x00020000, // Created for signature assignment check
15981611
ObjectLiteral = 0x00040000, // Originates in an object literal
1599-
/* @internal */
1612+
/* @internal */
16001613
ContainsUndefinedOrNull = 0x00080000, // Type is or contains Undefined or Null type
1601-
/* @internal */
1614+
/* @internal */
16021615
ContainsObjectLiteral = 0x00100000, // Type is or contains object literal type
16031616
ESSymbol = 0x00200000, // Type of symbol primitive introduced in ES6
16041617

@@ -1714,6 +1727,7 @@ module ts {
17141727
declaration: SignatureDeclaration; // Originating declaration
17151728
typeParameters: TypeParameter[]; // Type parameters (undefined if non-generic)
17161729
parameters: Symbol[]; // Parameters
1730+
typePredicate?: TypePredicate; // Type predicate
17171731
/* @internal */
17181732
resolvedReturnType: Type; // Resolved return type
17191733
/* @internal */

tests/baselines/reference/APISample_linter.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -75,26 +75,26 @@ function delint(sourceFile) {
7575
delintNode(sourceFile);
7676
function delintNode(node) {
7777
switch (node.kind) {
78-
case 187 /* ForStatement */:
79-
case 188 /* ForInStatement */:
80-
case 186 /* WhileStatement */:
81-
case 185 /* DoStatement */:
82-
if (node.statement.kind !== 180 /* Block */) {
78+
case 189 /* ForStatement */:
79+
case 190 /* ForInStatement */:
80+
case 188 /* WhileStatement */:
81+
case 187 /* DoStatement */:
82+
if (node.statement.kind !== 182 /* Block */) {
8383
report(node, "A looping statement's contents should be wrapped in a block body.");
8484
}
8585
break;
86-
case 184 /* IfStatement */:
86+
case 186 /* IfStatement */:
8787
var ifStatement = node;
88-
if (ifStatement.thenStatement.kind !== 180 /* Block */) {
88+
if (ifStatement.thenStatement.kind !== 182 /* Block */) {
8989
report(ifStatement.thenStatement, "An if statement's contents should be wrapped in a block body.");
9090
}
9191
if (ifStatement.elseStatement &&
92-
ifStatement.elseStatement.kind !== 180 /* Block */ &&
93-
ifStatement.elseStatement.kind !== 184 /* IfStatement */) {
92+
ifStatement.elseStatement.kind !== 182 /* Block */ &&
93+
ifStatement.elseStatement.kind !== 186 /* IfStatement */) {
9494
report(ifStatement.elseStatement, "An else statement's contents should be wrapped in a block body.");
9595
}
9696
break;
97-
case 170 /* BinaryExpression */:
97+
case 172 /* BinaryExpression */:
9898
var op = node.operatorToken.kind;
9999
if (op === 28 /* EqualsEqualsToken */ || op == 29 /* ExclamationEqualsToken */) {
100100
report(node, "Use '===' and '!=='.");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//// [typeGuardFunction.ts]
2+
3+
class A {
4+
propA: number;
5+
}
6+
7+
class B {
8+
propB: number;
9+
}
10+
11+
class C extends A {
12+
propC: number;
13+
}
14+
15+
declare function isA(p1: any): p1 is A;
16+
declare function isB(p1: any): p1 is B;
17+
declare function isC(p1: any): p1 is C;
18+
19+
declare function retC(): C;
20+
21+
var a: A;
22+
var b: B;
23+
24+
// Basic
25+
if (isC(a)) {
26+
a.propC;
27+
}
28+
29+
// Sub type
30+
var subType: C;
31+
if(isA(subType)) {
32+
subType.propC;
33+
}
34+
35+
// Union type
36+
var union: A | B;
37+
if(isA(union)) {
38+
union.propA;
39+
}
40+
41+
// Call signature
42+
interface I1 {
43+
(p1: A): p1 is C;
44+
}
45+
46+
// The parameter index and argument index for the type guard target is matching.
47+
// The type predicate type is assignable to the parameter type.
48+
declare function isC_multipleParams(p1, p2): p1 is C;
49+
if (isC_multipleParams(a, 0)) {
50+
a.propC;
51+
}
52+
53+
// Methods
54+
var obj: {
55+
func1(p1: A): p1 is C;
56+
}
57+
class D {
58+
method1(p1: A): p1 is C {
59+
return true;
60+
}
61+
}
62+
63+
// Arrow function
64+
let f1 = (p1: A): p1 is C => false;
65+
66+
// Function type
67+
declare function f2(p1: (p1: A) => p1 is C);
68+
69+
// Function expressions
70+
f2(function(p1: A): p1 is C {
71+
return true;
72+
});
73+
74+
// Evaluations are asssignable to boolean.
75+
declare function acceptingBoolean(a: boolean);
76+
acceptingBoolean(isA(a));
77+
78+
// Type predicates with different parameter name.
79+
declare function acceptingTypeGuardFunction(p1: (item) => item is A);
80+
acceptingTypeGuardFunction(isA);
81+
82+
// Binary expressions
83+
let union2: C | B;
84+
let union3: boolean | B = isA(union2) || union2;
85+
86+
//// [typeGuardFunction.js]
87+
var __extends = (this && this.__extends) || function (d, b) {
88+
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
89+
function __() { this.constructor = d; }
90+
__.prototype = b.prototype;
91+
d.prototype = new __();
92+
};
93+
var A = (function () {
94+
function A() {
95+
}
96+
return A;
97+
})();
98+
var B = (function () {
99+
function B() {
100+
}
101+
return B;
102+
})();
103+
var C = (function (_super) {
104+
__extends(C, _super);
105+
function C() {
106+
_super.apply(this, arguments);
107+
}
108+
return C;
109+
})(A);
110+
var a;
111+
var b;
112+
// Basic
113+
if (isC(a)) {
114+
a.propC;
115+
}
116+
// Sub type
117+
var subType;
118+
if (isA(subType)) {
119+
subType.propC;
120+
}
121+
// Union type
122+
var union;
123+
if (isA(union)) {
124+
union.propA;
125+
}
126+
if (isC_multipleParams(a, 0)) {
127+
a.propC;
128+
}
129+
// Methods
130+
var obj;
131+
var D = (function () {
132+
function D() {
133+
}
134+
D.prototype.method1 = function (p1) {
135+
return true;
136+
};
137+
return D;
138+
})();
139+
// Arrow function
140+
var f1 = function (p1) { return false; };
141+
// Function expressions
142+
f2(function (p1) {
143+
return true;
144+
});
145+
acceptingBoolean(isA(a));
146+
acceptingTypeGuardFunction(isA);
147+
// Binary expressions
148+
var union2;
149+
var union3 = isA(union2) || union2;

0 commit comments

Comments
 (0)