-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Changes from 6 commits
c4cff98
859cc53
d7208e8
cfe2cc3
d51dd84
b9f310d
be6e341
b6eb4ae
4594301
0228ec3
b0bfa0f
dc765b8
0284846
8238656
bc01b16
2885eb2
8e58694
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1632,6 +1632,14 @@ | |
"category": "Error", | ||
"code": 2517 | ||
}, | ||
"A 'this'-based type guard is not assignable to a parameter-based type guard": { | ||
"category": "Error", | ||
"code": 2518 | ||
}, | ||
"A 'this'-based type predicate is only allowed in class or interface members, get accessors, or return type positions for functions and methods": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "A 'this'-based type predicate is only allowed as the return type of functions, methods, and get accessors, or on members of classes and interfaces." |
||
"category": "Error", | ||
"code": 2519 | ||
}, | ||
"Duplicate identifier '{0}'. Compiler uses declaration '{1}' to support async functions.": { | ||
"category": "Error", | ||
"code": 2520 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
} | ||
|
@@ -756,7 +760,7 @@ namespace ts { | |
|
||
// @kind(SyntaxKind.TypePredicate) | ||
export interface TypePredicateNode extends TypeNode { | ||
parameterName: Identifier; | ||
parameterName: Identifier | ThisTypeNode; | ||
type: TypeNode; | ||
} | ||
|
||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
parameterName: string; | ||
parameterIndex: number; | ||
type: Type; | ||
} | ||
|
||
/* @internal */ | ||
|
@@ -2089,6 +2108,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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does a type predicate act as a type? I think this is the source of the orphaning issue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why special case type predicates everywhere with extra slots for them when they're mutually exclusive with a return type (if there is a type predicate, the return type must be boolean)? They even affect type equality (differently than booleans do) when they're nested within a type. Why not unify them all as predicate types? It would be fairly easy to unify them in this way, IMO. Yes, it being a type is why it follows assignments at present (like all types), but it shouldn't be hard to fix - it's just a matter of making the right branches of The more interesting issue I've noted with them right now is that class FSO {
isFile: this is File;
isNetworked: this is (this & Networked);
}
class File {
path: string;
}
interface Networked {
host: string;
}
let f: FSO = new File();
if (f.isFile) {
// f should be File
if (f.isNetworked) {
// f should be File & Networked, but is just File
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, that is an interesting issue with the nested 'this' type predicates. It does seem like it should work. The original reason why type predicates were special cased, was to prevent them from flowing around. That could be taken care of with widening, but it still requires type relations for type predicates. The main thing is that the a type predicate is in intimately tied to one of the parameters in its associated signature (or There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It certainly seems like it, given that we widen on assignment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed the issue - it was simply that |
||
|
||
/* @internal */ | ||
Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
@@ -2121,6 +2141,11 @@ namespace ts { | |
intrinsicName: string; // Name of intrinsic type | ||
} | ||
|
||
// Predicate types (TypeFlags.Predicate) | ||
export interface PredicateType extends Type { | ||
predicate: ThisTypePredicate; | ||
} | ||
|
||
// String literal types (TypeFlags.StringLiteral) | ||
export interface StringLiteralType extends Type { | ||
text: string; // Text of string literal | ||
|
@@ -2237,7 +2262,7 @@ namespace ts { | |
declaration: SignatureDeclaration; // Originating declaration | ||
typeParameters: TypeParameter[]; // Type parameters (undefined if non-generic) | ||
parameters: Symbol[]; // Parameters | ||
typePredicate?: TypePredicate; // Type predicate | ||
typePredicate?: ThisTypePredicate | IdentifierTypePredicate; // Type predicate | ||
/* @internal */ | ||
resolvedReturnType: Type; // Resolved return type | ||
/* @internal */ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
period, "assignable to or from" or "compatible with"