Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom type guard function #3262

Merged
merged 19 commits into from
Jun 9, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified bin/tsc
100644 → 100755
Empty file.
242 changes: 219 additions & 23 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ module ts {
Generators_are_not_allowed_in_an_ambient_context: { code: 1221, category: DiagnosticCategory.Error, key: "Generators are not allowed in an ambient context." },
An_overload_signature_cannot_be_declared_as_a_generator: { code: 1222, category: DiagnosticCategory.Error, key: "An overload signature cannot be declared as a generator." },
_0_tag_already_specified: { code: 1223, category: DiagnosticCategory.Error, key: "'{0}' tag already specified." },
Signature_0_must_have_a_type_predicate: { code: 1224, category: DiagnosticCategory.Error, key: "Signature '{0}' must have a type predicate." },
Cannot_find_parameter_0: { code: 1225, category: DiagnosticCategory.Error, key: "Cannot find parameter '{0}'." },
Type_predicate_0_is_not_assignable_to_1: { code: 1226, category: DiagnosticCategory.Error, key: "Type predicate '{0}' is not assignable to '{1}'." },
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}'." },
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." },
A_type_predicate_cannot_reference_a_rest_parameter: { code: 1229, category: DiagnosticCategory.Error, key: "A type predicate cannot reference a rest parameter." },
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." },
Duplicate_identifier_0: { code: 2300, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." },
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." },
Static_members_cannot_reference_class_type_parameters: { code: 2302, category: DiagnosticCategory.Error, key: "Static members cannot reference class type parameters." },
Expand Down
29 changes: 29 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,35 @@
"category": "Error",
"code": 1223
},
"Signature '{0}' must have a type predicate.": {
"category": "Error",
"code": 1224
},
"Cannot find parameter '{0}'.": {
"category": "Error",
"code": 1225
},
"Type predicate '{0}' is not assignable to '{1}'.": {
"category": "Error",
"code": 1226
},
"Parameter '{0}' is not in the same position as parameter '{1}'.": {
"category": "Error",
"code": 1227
},
"A type predicate is only allowed in return type position for functions and methods.": {
"category": "Error",
"code": 1228
},
"A type predicate cannot reference a rest parameter.": {
"category": "Error",
"code": 1229
},
"A type predicate cannot reference element '{0}' in a binding pattern.": {
"category": "Error",
"code": 1230
},


"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down
21 changes: 16 additions & 5 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ module ts {
case SyntaxKind.TypeReference:
return visitNode(cbNode, (<TypeReferenceNode>node).typeName) ||
visitNodes(cbNodes, (<TypeReferenceNode>node).typeArguments);
case SyntaxKind.TypePredicate:
return visitNode(cbNode, (<TypePredicateNode>node).parameterName) ||
visitNode(cbNode, (<TypePredicateNode>node).type);
case SyntaxKind.TypeQuery:
return visitNode(cbNode, (<TypeQueryNode>node).exprName);
case SyntaxKind.TypeLiteral:
Expand Down Expand Up @@ -1897,9 +1900,17 @@ module ts {

// TYPES

function parseTypeReference(): TypeReferenceNode {
let node = <TypeReferenceNode>createNode(SyntaxKind.TypeReference);
node.typeName = parseEntityName(/*allowReservedWords*/ false, Diagnostics.Type_expected);
function parseTypeReferenceOrTypePredicate(): TypeReferenceNode | TypePredicateNode {
let typeName = parseEntityName(/*allowReservedWords*/ false, Diagnostics.Type_expected);
if (typeName.kind === SyntaxKind.Identifier && token === SyntaxKind.IsKeyword) {
nextToken();
let node = <TypePredicateNode>createNode(SyntaxKind.TypePredicate, typeName.pos);
node.parameterName = <Identifier>typeName;
node.type = parseType();
return finishNode(node);
}
let node = <TypeReferenceNode>createNode(SyntaxKind.TypeReference, typeName.pos);
node.typeName = typeName;
if (!scanner.hasPrecedingLineBreak() && token === SyntaxKind.LessThanToken) {
node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken);
}
Expand Down Expand Up @@ -2336,7 +2347,7 @@ module ts {
case SyntaxKind.SymbolKeyword:
// If these are followed by a dot, then parse these out as a dotted type reference instead.
let node = tryParse(parseKeywordAndNoDot);
return node || parseTypeReference();
return node || parseTypeReferenceOrTypePredicate();
case SyntaxKind.VoidKeyword:
return parseTokenNode<TypeNode>();
case SyntaxKind.TypeOfKeyword:
Expand All @@ -2348,7 +2359,7 @@ module ts {
case SyntaxKind.OpenParenToken:
return parseParenthesizedType();
default:
return parseTypeReference();
return parseTypeReferenceOrTypePredicate();
}
}

Expand Down
1 change: 1 addition & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ module ts {
"in": SyntaxKind.InKeyword,
"instanceof": SyntaxKind.InstanceOfKeyword,
"interface": SyntaxKind.InterfaceKeyword,
"is": SyntaxKind.IsKeyword,
"let": SyntaxKind.LetKeyword,
"module": SyntaxKind.ModuleKeyword,
"namespace": SyntaxKind.NamespaceKeyword,
Expand Down
20 changes: 17 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ module ts {
ConstructorKeyword,
DeclareKeyword,
GetKeyword,
IsKeyword,
ModuleKeyword,
NamespaceKeyword,
RequireKeyword,
Expand Down Expand Up @@ -177,6 +178,7 @@ module ts {
ConstructSignature,
IndexSignature,
// Type
TypePredicate,
TypeReference,
FunctionType,
ConstructorType,
Expand Down Expand Up @@ -614,6 +616,11 @@ module ts {
typeArguments?: NodeArray<TypeNode>;
}

export interface TypePredicateNode extends TypeNode {
parameterName: Identifier;
type: TypeNode;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be TypeReferenceNode,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't TypeReferenceNode for non-primitives only? How about primitive types?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I believe TypeNode is indeed what you want

}

export interface TypeQueryNode extends TypeNode {
exprName: EntityName;
}
Expand Down Expand Up @@ -1386,6 +1393,12 @@ module ts {
NotAccessible,
CannotBeNamed
}

export interface TypePredicate {
parameterName: string;
parameterIndex: number;
type: Type;
}

/* @internal */
export type AnyImportSyntax = ImportDeclaration | ImportEqualsDeclaration;
Expand Down Expand Up @@ -1593,12 +1606,12 @@ module ts {
Union = 0x00004000, // Union
Anonymous = 0x00008000, // Anonymous
Instantiated = 0x00010000, // Instantiated anonymous type
/* @internal */
/* @internal */
FromSignature = 0x00020000, // Created for signature assignment check
ObjectLiteral = 0x00040000, // Originates in an object literal
/* @internal */
/* @internal */
ContainsUndefinedOrNull = 0x00080000, // Type is or contains Undefined or Null type
/* @internal */
/* @internal */
ContainsObjectLiteral = 0x00100000, // Type is or contains object literal type
ESSymbol = 0x00200000, // Type of symbol primitive introduced in ES6

Expand Down Expand Up @@ -1714,6 +1727,7 @@ module ts {
declaration: SignatureDeclaration; // Originating declaration
typeParameters: TypeParameter[]; // Type parameters (undefined if non-generic)
parameters: Symbol[]; // Parameters
typePredicate?: TypePredicate; // Type predicate
/* @internal */
resolvedReturnType: Type; // Resolved return type
/* @internal */
Expand Down
20 changes: 10 additions & 10 deletions tests/baselines/reference/APISample_linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,26 +75,26 @@ function delint(sourceFile) {
delintNode(sourceFile);
function delintNode(node) {
switch (node.kind) {
case 187 /* ForStatement */:
case 188 /* ForInStatement */:
case 186 /* WhileStatement */:
case 185 /* DoStatement */:
if (node.statement.kind !== 180 /* Block */) {
case 189 /* ForStatement */:
case 190 /* ForInStatement */:
case 188 /* WhileStatement */:
case 187 /* DoStatement */:
if (node.statement.kind !== 182 /* Block */) {
report(node, "A looping statement's contents should be wrapped in a block body.");
}
break;
case 184 /* IfStatement */:
case 186 /* IfStatement */:
var ifStatement = node;
if (ifStatement.thenStatement.kind !== 180 /* Block */) {
if (ifStatement.thenStatement.kind !== 182 /* Block */) {
report(ifStatement.thenStatement, "An if statement's contents should be wrapped in a block body.");
}
if (ifStatement.elseStatement &&
ifStatement.elseStatement.kind !== 180 /* Block */ &&
ifStatement.elseStatement.kind !== 184 /* IfStatement */) {
ifStatement.elseStatement.kind !== 182 /* Block */ &&
ifStatement.elseStatement.kind !== 186 /* IfStatement */) {
report(ifStatement.elseStatement, "An else statement's contents should be wrapped in a block body.");
}
break;
case 170 /* BinaryExpression */:
case 172 /* BinaryExpression */:
var op = node.operatorToken.kind;
if (op === 28 /* EqualsEqualsToken */ || op == 29 /* ExclamationEqualsToken */) {
report(node, "Use '===' and '!=='.");
Expand Down
149 changes: 149 additions & 0 deletions tests/baselines/reference/typeGuardFunction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//// [typeGuardFunction.ts]

class A {
propA: number;
}

class B {
propB: number;
}

class C extends A {
propC: number;
}

declare function isA(p1: any): p1 is A;
declare function isB(p1: any): p1 is B;
declare function isC(p1: any): p1 is C;

declare function retC(): C;

var a: A;
var b: B;

// Basic
if (isC(a)) {
a.propC;
}

// Sub type
var subType: C;
if(isA(subType)) {
subType.propC;
}

// Union type
var union: A | B;
if(isA(union)) {
union.propA;
}

// Call signature
interface I1 {
(p1: A): p1 is C;
}

// The parameter index and argument index for the type guard target is matching.
// The type predicate type is assignable to the parameter type.
declare function isC_multipleParams(p1, p2): p1 is C;
if (isC_multipleParams(a, 0)) {
a.propC;
}

// Methods
var obj: {
func1(p1: A): p1 is C;
}
class D {
method1(p1: A): p1 is C {
return true;
}
}

// Arrow function
let f1 = (p1: A): p1 is C => false;

// Function type
declare function f2(p1: (p1: A) => p1 is C);

// Function expressions
f2(function(p1: A): p1 is C {
return true;
});

// Evaluations are asssignable to boolean.
declare function acceptingBoolean(a: boolean);
acceptingBoolean(isA(a));

// Type predicates with different parameter name.
declare function acceptingTypeGuardFunction(p1: (item) => item is A);
acceptingTypeGuardFunction(isA);

// Binary expressions
let union2: C | B;
let union3: boolean | B = isA(union2) || union2;

//// [typeGuardFunction.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; }
__.prototype = b.prototype;
d.prototype = new __();
};
var A = (function () {
function A() {
}
return A;
})();
var B = (function () {
function B() {
}
return B;
})();
var C = (function (_super) {
__extends(C, _super);
function C() {
_super.apply(this, arguments);
}
return C;
})(A);
var a;
var b;
// Basic
if (isC(a)) {
a.propC;
}
// Sub type
var subType;
if (isA(subType)) {
subType.propC;
}
// Union type
var union;
if (isA(union)) {
union.propA;
}
if (isC_multipleParams(a, 0)) {
a.propC;
}
// Methods
var obj;
var D = (function () {
function D() {
}
D.prototype.method1 = function (p1) {
return true;
};
return D;
})();
// Arrow function
var f1 = function (p1) { return false; };
// Function expressions
f2(function (p1) {
return true;
});
acceptingBoolean(isA(a));
acceptingTypeGuardFunction(isA);
// Binary expressions
var union2;
var union3 = isA(union2) || union2;
Loading