Adds type guard methods

This commit is contained in:
Tingan Ho 2015-06-02 23:25:21 +08:00
parent f8e2b99b6c
commit b7d1df68fb
7 changed files with 350 additions and 23 deletions

View file

@ -5646,6 +5646,24 @@ module ts {
return type;
}
function shouldNarrowTypeByTypePredicate(signature: Signature, expr: CallExpression): boolean {
if (!signature.typePredicate) {
return false;
}
if (expr.arguments &&
expr.arguments[signature.typePredicate.parameterIndex] &&
getSymbolAtLocation(expr.arguments[signature.typePredicate.parameterIndex]) === symbol) {
return true;
}
if (expr.expression.kind === SyntaxKind.PropertyAccessExpression &&
getSymbolAtLocation((<PropertyAccessExpression>expr.expression).expression) === symbol) {
return true;
}
return false;
}
function narrowTypeByTypePredicate(type: Type, expr: CallExpression, assumeTrue: boolean): Type {
if (type.flags & TypeFlags.Any) {
return type;
@ -5657,16 +5675,12 @@ module ts {
}
return type;
}
if (signature.typePredicate) {
if (expr.arguments && expr.arguments[signature.typePredicate.parameterIndex]) {
if (getSymbolAtLocation(expr.arguments[signature.typePredicate.parameterIndex]) === symbol) {
if (isTypeSubtypeOf(signature.typePredicate.type, type)) {
return signature.typePredicate.type;
}
if (type.flags & TypeFlags.Union) {
return getUnionType(filter((<UnionType>type).types, t => isTypeSubtypeOf(t, signature.typePredicate.type)));
}
}
if (shouldNarrowTypeByTypePredicate(signature, expr)) {
if (isTypeSubtypeOf(signature.typePredicate.type, type)) {
return signature.typePredicate.type;
}
if (type.flags & TypeFlags.Union) {
return getUnionType(filter((<UnionType>type).types, t => isTypeSubtypeOf(t, signature.typePredicate.type)));
}
}
return type;
@ -8622,8 +8636,7 @@ module ts {
links.typeFromTypePredicate = getTypeFromTypeNode(node.typePredicate.type);
}
if (links.typePredicateParameterIndex >= 0) {
checkTypeAssignableTo(
links.typeFromTypePredicate,
checkTypeAssignableTo(links.typeFromTypePredicate,
getTypeAtLocation(node.parameters[links.typePredicateParameterIndex]),
node.typePredicate.type);
}
@ -8632,6 +8645,17 @@ module ts {
Diagnostics.Cannot_find_parameter_0,
node.typePredicate.parameterName.text);
}
else {
let typeOfClass = getTypeAtLocation(node.parent);
if (!isTypeSubtypeOf(links.typeFromTypePredicate, typeOfClass) &&
!isTypeSubtypeOf(typeOfClass, links.typeFromTypePredicate)) {
error(node.typePredicate,
Diagnostics.Type_0_and_type_1_are_disjoint_types,
typeToString(links.typeFromTypePredicate),
typeToString(typeOfClass));
}
}
}
if (produceDiagnostics) {

View file

@ -184,6 +184,7 @@ module ts {
Cannot_find_parameter_0: { code: 1226, category: DiagnosticCategory.Error, key: "Cannot find parameter '{0}'." },
Type_guard_annotation_0_is_not_assignable_to_1: { code: 1227, category: DiagnosticCategory.Error, key: "Type-guard annotation '{0}' is not assignable to '{1}'." },
Parameter_index_from_0_does_not_match_the_parameter_index_from_1: { code: 1228, category: DiagnosticCategory.Error, key: "Parameter index from '{0}' does not match the parameter index from '{1}'." },
Type_0_and_type_1_are_disjoint_types: { code: 1229, category: DiagnosticCategory.Error, key: "Type '{0}' and type '{1}' are disjoint types." },
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." },

View file

@ -723,6 +723,10 @@
"category": "Error",
"code": 1228
},
"Type '{0}' and type '{1}' are disjoint types.": {
"category": "Error",
"code": 1229
},
"Duplicate identifier '{0}'.": {

View file

@ -2014,6 +2014,34 @@ module ts {
return parseInitializer(/*inParameter*/ true);
}
function parseTypePredicate(signature: SignatureDeclaration) {
let node = <TypePredicateNode>createNode(SyntaxKind.TypePredicate);
if (token !== SyntaxKind.ThisKeyword) {
node.pos = signature.type.pos;
node.parameterName = <Identifier>(<TypeReferenceNode>signature.type).typeName;
signature.type = undefined;
}
else {
// Swallow `this`
nextToken();
}
// Swallow `is`
nextToken();
node.type = parseType();
signature.typePredicate = finishNode(node);
}
function parseTypePredicateOrReturnType(signature: SignatureDeclaration) {
if (token === SyntaxKind.ThisKeyword) {
parseTypePredicate(signature);
}
else {
signature.type = parseType();
}
}
function fillSignature(
returnToken: SyntaxKind,
yieldAndGeneratorParameterContext: boolean,
@ -2025,21 +2053,13 @@ module ts {
if (returnTokenRequired) {
parseExpected(returnToken);
signature.type = parseType();
parseTypePredicateOrReturnType(signature);
}
else if (parseOptional(returnToken)) {
signature.type = parseType();
parseTypePredicateOrReturnType(signature);
}
if (token === SyntaxKind.IsKeyword) {
let node = <TypePredicateNode>createNode(SyntaxKind.TypePredicate);
node.pos = signature.type.pos;
node.parameterName = <Identifier>(<TypeReferenceNode>signature.type).typeName;
nextToken();
node.type = parseType();
signature.type = undefined;
signature.typePredicate = finishNode(node);
parseTypePredicate(signature);
}
}

View file

@ -0,0 +1,73 @@
tests/cases/conformance/expressions/typeGuards/typeGuardMethods.ts(14,12): error TS1229: Type 'A' and type 'B' are disjoint types.
tests/cases/conformance/expressions/typeGuards/typeGuardMethods.ts(17,12): error TS1229: Type 'C' and type 'B' are disjoint types.
==== tests/cases/conformance/expressions/typeGuards/typeGuardMethods.ts (2 errors) ====
class A {
propA: number;
isA(): this is A {
return true;
}
isC(): this is C {
return false;
}
}
class B {
propB: number;
isA(): this is A {
~~~~~~~~~
!!! error TS1229: Type 'A' and type 'B' are disjoint types.
return false;
}
isC(): this is C {
~~~~~~~~~
!!! error TS1229: Type 'C' and type 'B' are disjoint types.
return false;
}
}
class C extends A {
propC: number;
isA(): this is A {
return false;
}
isC(): this is C {
return true;
}
}
class D extends C {
isA(): this is A {
return false;
}
isString(x: any): x is string { // with parameter declaration
return true;
}
}
var a: A;
// Basic.
if (a.isC()) {
a.propC;
}
// Sub type.
var subType: C;
if(subType.isA()) {
subType.propC;
}
// Union type.
var union: A | B;
if(union.isA()) {
union.propA;
}
var b: any;
var d = new D;
if(d.isString(b)) {
b.length;
}

View file

@ -0,0 +1,141 @@
//// [typeGuardMethods.ts]
class A {
propA: number;
isA(): this is A {
return true;
}
isC(): this is C {
return false;
}
}
class B {
propB: number;
isA(): this is A {
return false;
}
isC(): this is C {
return false;
}
}
class C extends A {
propC: number;
isA(): this is A {
return false;
}
isC(): this is C {
return true;
}
}
class D extends C {
isA(): this is A {
return false;
}
isString(x: any): x is string { // with parameter declaration
return true;
}
}
var a: A;
// Basic.
if (a.isC()) {
a.propC;
}
// Sub type.
var subType: C;
if(subType.isA()) {
subType.propC;
}
// Union type.
var union: A | B;
if(union.isA()) {
union.propA;
}
var b: any;
var d = new D;
if(d.isString(b)) {
b.length;
}
//// [typeGuardMethods.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() {
}
A.prototype.isA = function () {
return true;
};
A.prototype.isC = function () {
return false;
};
return A;
})();
var B = (function () {
function B() {
}
B.prototype.isA = function () {
return false;
};
B.prototype.isC = function () {
return false;
};
return B;
})();
var C = (function (_super) {
__extends(C, _super);
function C() {
_super.apply(this, arguments);
}
C.prototype.isA = function () {
return false;
};
C.prototype.isC = function () {
return true;
};
return C;
})(A);
var D = (function (_super) {
__extends(D, _super);
function D() {
_super.apply(this, arguments);
}
D.prototype.isA = function () {
return false;
};
D.prototype.isString = function (x) {
return true;
};
return D;
})(C);
var a;
// Basic.
if (a.isC()) {
a.propC;
}
// Sub type.
var subType;
if (subType.isA()) {
subType.propC;
}
// Union type.
var union;
if (union.isA()) {
union.propA;
}
var b;
var d = new D;
if (d.isString(b)) {
b.length;
}

View file

@ -0,0 +1,64 @@
class A {
propA: number;
isA(): this is A {
return true;
}
isC(): this is C {
return false;
}
}
class B {
propB: number;
isA(): this is A {
return false;
}
isC(): this is C {
return false;
}
}
class C extends A {
propC: number;
isA(): this is A {
return false;
}
isC(): this is C {
return true;
}
}
class D extends C {
isA(): this is A {
return false;
}
isString(x: any): x is string { // with parameter declaration
return true;
}
}
var a: A;
// Basic.
if (a.isC()) {
a.propC;
}
// Sub type.
var subType: C;
if(subType.isA()) {
subType.propC;
}
// Union type.
var union: A | B;
if(union.isA()) {
union.propA;
}
var b: any;
var d = new D;
if(d.isString(b)) {
b.length;
}