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

This type predicates for type guards #5906

Merged
merged 17 commits into from
Dec 10, 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
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) {
Copy link
Member

Choose a reason for hiding this comment

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

Consider just destructuring parameterName and type

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

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 {
Copy link
Member

Choose a reason for hiding this comment

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

It seems like overkill to have two different kinds of TypePredicate types and a dedicated enum. Could you just use parameterIndex = -1 to indicate this?

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,
Copy link
Member

Choose a reason for hiding this comment

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

More than anything else, can you leave a comment about the interaction here with intrinsicName?

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 @@ -692,6 +692,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