Skip to content

Commit

Permalink
Merge pull request #5906 from weswigham/this-type-guards
Browse files Browse the repository at this point in the history
This type predicates for type guards
  • Loading branch information
weswigham committed Dec 10, 2015
2 parents 6e06752 + 8e58694 commit 58400ed
Show file tree
Hide file tree
Showing 30 changed files with 2,942 additions and 177 deletions.
14 changes: 13 additions & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1189,7 +1189,8 @@ namespace ts {
case SyntaxKind.ThisType:
seenThisKeyword = true;
return;

case SyntaxKind.TypePredicate:
return checkTypePredicate(node as TypePredicateNode);
case SyntaxKind.TypeParameter:
return declareSymbolAndAddToSymbolTable(<Declaration>node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
case SyntaxKind.Parameter:
Expand Down Expand Up @@ -1275,6 +1276,17 @@ namespace ts {
}
}

function checkTypePredicate(node: TypePredicateNode) {
const { parameterName, type } = node;
if (parameterName && parameterName.kind === SyntaxKind.Identifier) {
checkStrictModeIdentifier(parameterName as Identifier);
}
if (parameterName && parameterName.kind === SyntaxKind.ThisType) {
seenThisKeyword = true;
}
bind(type);
}

function bindSourceFileIfExternalModule() {
setExportContextFlag(file);
if (isExternalModule(file)) {
Expand Down
360 changes: 240 additions & 120 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1647,6 +1647,14 @@
"category": "Error",
"code": 2517
},
"A 'this'-based type guard is not compatible with a parameter-based type guard.": {
"category": "Error",
"code": 2518
},
"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.": {
"category": "Error",
"code": 2519
},
"Duplicate identifier '{0}'. Compiler uses declaration '{1}' to support async functions.": {
"category": "Error",
"code": 2520
Expand Down
29 changes: 20 additions & 9 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1963,11 +1963,7 @@ namespace ts {
function parseTypeReferenceOrTypePredicate(): TypeReferenceNode | TypePredicateNode {
const typeName = parseEntityName(/*allowReservedWords*/ false, Diagnostics.Type_expected);
if (typeName.kind === SyntaxKind.Identifier && token === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) {
nextToken();
const node = <TypePredicateNode>createNode(SyntaxKind.TypePredicate, typeName.pos);
node.parameterName = <Identifier>typeName;
node.type = parseType();
return finishNode(node);
return parseTypePredicate(typeName as Identifier);
}
const node = <TypeReferenceNode>createNode(SyntaxKind.TypeReference, typeName.pos);
node.typeName = typeName;
Expand All @@ -1977,8 +1973,16 @@ namespace ts {
return finishNode(node);
}

function parseThisTypeNode(): TypeNode {
const node = <TypeNode>createNode(SyntaxKind.ThisType);
function parseTypePredicate(lhs: Identifier | ThisTypeNode): TypePredicateNode {
nextToken();
const node = createNode(SyntaxKind.TypePredicate, lhs.pos) as TypePredicateNode;
node.parameterName = lhs;
node.type = parseType();
return finishNode(node);
}

function parseThisTypeNode(): ThisTypeNode {
const node = createNode(SyntaxKind.ThisType) as ThisTypeNode;
nextToken();
return finishNode(node);
}
Expand Down Expand Up @@ -2424,8 +2428,15 @@ namespace ts {
return parseStringLiteralTypeNode();
case SyntaxKind.VoidKeyword:
return parseTokenNode<TypeNode>();
case SyntaxKind.ThisKeyword:
return parseThisTypeNode();
case SyntaxKind.ThisKeyword: {
const thisKeyword = parseThisTypeNode();
if (token === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) {
return parseTypePredicate(thisKeyword);
}
else {
return thisKeyword;
}
}
case SyntaxKind.TypeOfKeyword:
return parseTypeQuery();
case SyntaxKind.OpenBraceToken:
Expand Down
34 changes: 29 additions & 5 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,11 +733,15 @@ namespace ts {
// @kind(SyntaxKind.StringKeyword)
// @kind(SyntaxKind.SymbolKeyword)
// @kind(SyntaxKind.VoidKeyword)
// @kind(SyntaxKind.ThisType)
export interface TypeNode extends Node {
_typeNodeBrand: any;
}

// @kind(SyntaxKind.ThisType)
export interface ThisTypeNode extends TypeNode {
_thisTypeNodeBrand: any;
}

export interface FunctionOrConstructorTypeNode extends TypeNode, SignatureDeclaration {
_functionOrConstructorTypeNodeBrand: any;
}
Expand All @@ -756,7 +760,7 @@ namespace ts {

// @kind(SyntaxKind.TypePredicate)
export interface TypePredicateNode extends TypeNode {
parameterName: Identifier;
parameterName: Identifier | ThisTypeNode;
type: TypeNode;
}

Expand Down Expand Up @@ -1820,10 +1824,25 @@ namespace ts {
CannotBeNamed
}

export const enum TypePredicateKind {
This,
Identifier
}

export interface TypePredicate {
kind: TypePredicateKind;
type: Type;
}

// @kind (TypePredicateKind.This)
export interface ThisTypePredicate extends TypePredicate {
_thisTypePredicateBrand: any;
}

// @kind (TypePredicateKind.Identifier)
export interface IdentifierTypePredicate extends TypePredicate {
parameterName: string;
parameterIndex: number;
type: Type;
}

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

/* @internal */
Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null,
Expand All @@ -2102,7 +2122,7 @@ namespace ts {
UnionOrIntersection = Union | Intersection,
StructuredType = ObjectType | Union | Intersection,
/* @internal */
RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral,
RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral | PredicateType,
/* @internal */
PropagatingFlags = ContainsUndefinedOrNull | ContainsObjectLiteral | ContainsAnyFunctionType
}
Expand All @@ -2123,6 +2143,11 @@ namespace ts {
intrinsicName: string; // Name of intrinsic type
}

// Predicate types (TypeFlags.Predicate)
export interface PredicateType extends Type {
predicate: ThisTypePredicate | IdentifierTypePredicate;
}

// String literal types (TypeFlags.StringLiteral)
export interface StringLiteralType extends Type {
text: string; // Text of string literal
Expand Down Expand Up @@ -2239,7 +2264,6 @@ namespace 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
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,10 @@ namespace ts {
return node && node.kind === SyntaxKind.MethodDeclaration && node.parent.kind === SyntaxKind.ObjectLiteralExpression;
}

export function isIdentifierTypePredicate(predicate: TypePredicate): predicate is IdentifierTypePredicate {
return predicate && predicate.kind === TypePredicateKind.Identifier;
}

export function getContainingFunction(node: Node): FunctionLikeDeclaration {
while (true) {
node = node.parent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ var obj: Object;
>Object : Object

if (ArrayBuffer.isView(obj)) {
>ArrayBuffer.isView(obj) : boolean
>ArrayBuffer.isView(obj) : arg is ArrayBufferView
>ArrayBuffer.isView : (arg: any) => arg is ArrayBufferView
>ArrayBuffer : ArrayBufferConstructor
>isView : (arg: any) => arg is ArrayBufferView
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/isArray.types
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ var maybeArray: number | number[];


if (Array.isArray(maybeArray)) {
>Array.isArray(maybeArray) : boolean
>Array.isArray(maybeArray) : arg is any[]
>Array.isArray : (arg: any) => arg is any[]
>Array : ArrayConstructor
>isArray : (arg: any) => arg is any[]
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/stringLiteralCheckedInIf02.types
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function f(foo: T) {
>T : ("a" | "b")[] | "a" | "b"

if (isS(foo)) {
>isS(foo) : boolean
>isS(foo) : t is "a" | "b"
>isS : (t: ("a" | "b")[] | "a" | "b") => t is "a" | "b"
>foo : ("a" | "b")[] | "a" | "b"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts(18,10): error TS2382: Specialized overload signature is not assignable to any non-specialized signature.
tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts(19,10): error TS2382: Specialized overload signature is not assignable to any non-specialized signature.
tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts(20,10): error TS2394: Overload signature is not compatible with function implementation.
tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts(22,21): error TS2304: Cannot find name 'is'.


==== tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts (4 errors) ====
==== tests/cases/conformance/types/stringLiteral/stringLiteralTypesAsTags01.ts (2 errors) ====

type Kind = "A" | "B"

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

function hasKind(entity: Entity, kind: "A"): entity is A;
~~~~~~~
!!! error TS2382: Specialized overload signature is not assignable to any non-specialized signature.
function hasKind(entity: Entity, kind: "B"): entity is B;
~~~~~~~
!!! error TS2382: Specialized overload signature is not assignable to any non-specialized signature.
function hasKind(entity: Entity, kind: Kind): entity is Entity;
~~~~~~~
!!! error TS2394: Overload signature is not compatible with function implementation.
Expand Down
14 changes: 7 additions & 7 deletions tests/baselines/reference/typeGuardFunction.types
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ var b: B;

// Basic
if (isC(a)) {
>isC(a) : boolean
>isC(a) : p1 is C
>isC : (p1: any) => p1 is C
>a : A

Expand All @@ -70,7 +70,7 @@ var subType: C;
>C : C

if(isA(subType)) {
>isA(subType) : boolean
>isA(subType) : p1 is A
>isA : (p1: any) => p1 is A
>subType : C

Expand All @@ -87,7 +87,7 @@ var union: A | B;
>B : B

if(isA(union)) {
>isA(union) : boolean
>isA(union) : p1 is A
>isA : (p1: any) => p1 is A
>union : A | B

Expand Down Expand Up @@ -118,7 +118,7 @@ declare function isC_multipleParams(p1, p2): p1 is C;
>C : C

if (isC_multipleParams(a, 0)) {
>isC_multipleParams(a, 0) : boolean
>isC_multipleParams(a, 0) : p1 is C
>isC_multipleParams : (p1: any, p2: any) => p1 is C
>a : A
>0 : number
Expand Down Expand Up @@ -197,7 +197,7 @@ declare function acceptingBoolean(a: boolean);
acceptingBoolean(isA(a));
>acceptingBoolean(isA(a)) : any
>acceptingBoolean : (a: boolean) => any
>isA(a) : boolean
>isA(a) : p1 is A
>isA : (p1: any) => p1 is A
>a : A

Expand All @@ -223,8 +223,8 @@ let union2: C | B;
let union3: boolean | B = isA(union2) || union2;
>union3 : boolean | B
>B : B
>isA(union2) || union2 : boolean | B
>isA(union2) : boolean
>isA(union2) || union2 : p1 is A | B
>isA(union2) : p1 is A
>isA : (p1: any) => p1 is A
>union2 : C | B
>union2 : B
Expand Down
12 changes: 7 additions & 5 deletions tests/baselines/reference/typeGuardFunctionErrors.errors.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(15,12): error TS2322: Type 'string' is not assignable to type 'boolean'.
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(15,12): error TS2322: Type 'string' is not assignable to type 'x is A'.
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(22,33): error TS2304: Cannot find name 'x'.
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(26,33): error TS1225: Cannot find parameter 'x'.
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(30,10): error TS2391: Function implementation is missing or not immediately following the declaration.
Expand All @@ -16,6 +16,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(70,7):
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'.
Type predicate 'p1 is C' is not assignable to 'p1 is B'.
Type 'C' is not assignable to type 'B'.
Property 'propB' is missing in type 'C'.
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'.
Signature '(p1: any, p2: any): boolean' must have a type predicate.
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'.
Expand All @@ -25,14 +26,14 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(91,1):
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.
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.
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.
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.
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(105,16): error TS2322: Type 'boolean' is not assignable to type 'D'.
Property 'm1' is missing in type 'Boolean'.
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
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.
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.
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(111,16): error TS2408: Setters cannot return a value.
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.
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(120,22): error TS1225: Cannot find parameter 'p1'.
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.
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(124,20): error TS1229: A type predicate cannot reference a rest parameter.
tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(129,34): error TS1230: A type predicate cannot reference element 'p1' in a binding pattern.
Expand All @@ -57,7 +58,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(137,39
function hasANonBooleanReturnStatement(x): x is A {
return '';
~~
!!! error TS2322: Type 'string' is not assignable to type 'boolean'.
!!! error TS2322: Type 'string' is not assignable to type 'x is A'.
}

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

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

interface I2 {
[index: number]: p1 is C;
~~
!!! error TS1225: Cannot find parameter 'p1'.
~~~~~~~
!!! error TS1228: A type predicate is only allowed in return type position for functions and methods.
}
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/typeGuardFunctionGenerics.types
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ let test1: boolean = funA(isB);
>isB : (p1: any) => p1 is B

if (funB(retC, a)) {
>funB(retC, a) : boolean
>funB(retC, a) : p2 is C
>funB : <T>(p1: (p1: any) => T, p2: any) => p2 is T
>retC : (x: any) => C
>a : A
Expand All @@ -118,7 +118,7 @@ let test2: B = funC(isB);
>isB : (p1: any) => p1 is B

if (funD(isC, a)) {
>funD(isC, a) : boolean
>funD(isC, a) : p2 is C
>funD : <T>(p1: (p1: any) => p1 is T, p2: any) => p2 is T
>isC : (p1: any) => p1 is C
>a : A
Expand Down
Loading

0 comments on commit 58400ed

Please sign in to comment.