Skip to content

Commit 373a776

Browse files
committed
Adds custom type guard
1 parent 2cb0dfd commit 373a776

File tree

6 files changed

+209
-17
lines changed

6 files changed

+209
-17
lines changed

src/compiler/checker.ts

+146-14
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ module ts {
9797
let anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
9898
let noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
9999

100-
let anySignature = createSignature(undefined, undefined, emptyArray, anyType, 0, false, false);
101-
let unknownSignature = createSignature(undefined, undefined, emptyArray, unknownType, 0, false, false);
100+
let anySignature = createSignature(undefined, undefined, emptyArray, anyType, undefined, 0, false, false);
101+
let unknownSignature = createSignature(undefined, undefined, emptyArray, unknownType, undefined, 0, false, false);
102102

103103
let globals: SymbolTable = {};
104104

@@ -2766,7 +2766,7 @@ module ts {
27662766

27672767
function resolveDeclaredMembers(type: InterfaceType): InterfaceTypeWithDeclaredMembers {
27682768
if (!(<InterfaceTypeWithDeclaredMembers>type).declaredProperties) {
2769-
var symbol = type.symbol;
2769+
let symbol = type.symbol;
27702770
(<InterfaceTypeWithDeclaredMembers>type).declaredProperties = getNamedMembers(symbol.members);
27712771
(<InterfaceTypeWithDeclaredMembers>type).declaredCallSignatures = getSignaturesOfSymbol(symbol.members["__call"]);
27722772
(<InterfaceTypeWithDeclaredMembers>type).declaredConstructSignatures = getSignaturesOfSymbol(symbol.members["__new"]);
@@ -2776,7 +2776,7 @@ module ts {
27762776
return <InterfaceTypeWithDeclaredMembers>type;
27772777
}
27782778

2779-
function resolveClassOrInterfaceMembers(type: InterfaceType): void {
2779+
function resolveClassOrInterfaceMembers(type: InterfaceType) {
27802780
let target = resolveDeclaredMembers(type);
27812781
let members = target.symbol.members;
27822782
let callSignatures = target.declaredCallSignatures;
@@ -2817,20 +2817,21 @@ module ts {
28172817
}
28182818

28192819
function createSignature(declaration: SignatureDeclaration, typeParameters: TypeParameter[], parameters: Symbol[],
2820-
resolvedReturnType: Type, minArgumentCount: number, hasRestParameter: boolean, hasStringLiterals: boolean): Signature {
2820+
resolvedReturnType: Type, typePredicate: TypePredicate, minArgumentCount: number, hasRestParameter: boolean, hasStringLiterals: boolean): Signature {
28212821
let sig = new Signature(checker);
28222822
sig.declaration = declaration;
28232823
sig.typeParameters = typeParameters;
28242824
sig.parameters = parameters;
28252825
sig.resolvedReturnType = resolvedReturnType;
2826+
sig.typePredicate = typePredicate;
28262827
sig.minArgumentCount = minArgumentCount;
28272828
sig.hasRestParameter = hasRestParameter;
28282829
sig.hasStringLiterals = hasStringLiterals;
28292830
return sig;
28302831
}
28312832

28322833
function cloneSignature(sig: Signature): Signature {
2833-
return createSignature(sig.declaration, sig.typeParameters, sig.parameters, sig.resolvedReturnType,
2834+
return createSignature(sig.declaration, sig.typeParameters, sig.parameters, sig.resolvedReturnType, sig.typePredicate,
28342835
sig.minArgumentCount, sig.hasRestParameter, sig.hasStringLiterals);
28352836
}
28362837

@@ -2847,7 +2848,7 @@ module ts {
28472848
return signature;
28482849
});
28492850
}
2850-
return [createSignature(undefined, classType.localTypeParameters, emptyArray, classType, 0, false, false)];
2851+
return [createSignature(undefined, classType.localTypeParameters, emptyArray, classType, undefined, 0, false, false)];
28512852
}
28522853

28532854
function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable {
@@ -3219,7 +3220,24 @@ module ts {
32193220
}
32203221

32213222
let returnType: Type;
3222-
if (classType) {
3223+
let typePredicate: TypePredicate;
3224+
if (declaration.typePredicate) {
3225+
returnType = booleanType;
3226+
let typePredicateNode = declaration.typePredicate;
3227+
let links = getNodeLinks(typePredicateNode);
3228+
if (links.typePredicateParameterIndex === undefined) {
3229+
links.typePredicateParameterIndex = getTypePredicateParameterIndex(declaration.parameters, typePredicateNode.parameterName);
3230+
}
3231+
if (!links.typeFromTypePredicate) {
3232+
links.typeFromTypePredicate = getTypeFromTypeNode(declaration.typePredicate.type);
3233+
}
3234+
typePredicate = {
3235+
parameterName: typePredicateNode.parameterName ? typePredicateNode.parameterName.text : undefined,
3236+
parameterIndex: typePredicateNode.parameterName ? links.typePredicateParameterIndex : undefined,
3237+
type: links.typeFromTypePredicate
3238+
};
3239+
}
3240+
else if (classType) {
32233241
returnType = classType;
32243242
}
32253243
else if (declaration.type) {
@@ -3238,7 +3256,7 @@ module ts {
32383256
}
32393257
}
32403258

3241-
links.resolvedSignature = createSignature(declaration, typeParameters, parameters, returnType,
3259+
links.resolvedSignature = createSignature(declaration, typeParameters, parameters, returnType, typePredicate,
32423260
minArgumentCount, hasRestParameters(declaration), hasStringLiterals);
32433261
}
32443262
return links.resolvedSignature;
@@ -3944,9 +3962,13 @@ module ts {
39443962
freshTypeParameters = instantiateList(signature.typeParameters, mapper, instantiateTypeParameter);
39453963
mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper);
39463964
}
3965+
if (signature.typePredicate) {
3966+
signature.typePredicate.type = instantiateType(signature.typePredicate.type, mapper);
3967+
}
39473968
let result = createSignature(signature.declaration, freshTypeParameters,
39483969
instantiateList(signature.parameters, mapper, instantiateSymbol),
39493970
signature.resolvedReturnType ? instantiateType(signature.resolvedReturnType, mapper) : undefined,
3971+
signature.typePredicate,
39503972
signature.minArgumentCount, signature.hasRestParameter, signature.hasStringLiterals);
39513973
result.target = signature;
39523974
result.mapper = mapper;
@@ -4614,6 +4636,43 @@ module ts {
46144636
}
46154637
result &= related;
46164638
}
4639+
4640+
if (source.typePredicate && target.typePredicate) {
4641+
if (source.typePredicate.parameterIndex !== target.typePredicate.parameterIndex ||
4642+
source.typePredicate.type.symbol !== target.typePredicate.type.symbol) {
4643+
4644+
if (reportErrors) {
4645+
let sourceParamText = source.typePredicate.parameterName;
4646+
let targetParamText = target.typePredicate.parameterName;
4647+
let sourceTypeText = typeToString(source.typePredicate.type);
4648+
let targetTypeText = typeToString(target.typePredicate.type);
4649+
4650+
if (source.typePredicate.parameterIndex !== target.typePredicate.parameterIndex) {
4651+
reportError(Diagnostics.Parameter_index_from_0_does_not_match_the_parameter_index_from_1,
4652+
sourceParamText,
4653+
targetParamText);
4654+
}
4655+
if (source.typePredicate.type.symbol !== target.typePredicate.type.symbol) {
4656+
reportError(Diagnostics.Type_0_is_not_assignable_to_type_1,
4657+
sourceTypeText,
4658+
targetTypeText);
4659+
}
4660+
4661+
reportError(Diagnostics.Type_guard_annotation_0_is_not_assignable_to_1,
4662+
`${sourceParamText} is ${sourceTypeText}`,
4663+
`${targetParamText} is ${targetTypeText}`);
4664+
}
4665+
4666+
return Ternary.False;
4667+
}
4668+
}
4669+
else if (!source.typePredicate && target.typePredicate) {
4670+
if (reportErrors) {
4671+
reportError(Diagnostics.A_non_type_guard_function_is_not_assignable_to_a_type_guard_function);
4672+
}
4673+
return Ternary.False;
4674+
}
4675+
46174676
let t = getReturnTypeOfSignature(target);
46184677
if (t === voidType) return result;
46194678
let s = getReturnTypeOfSignature(source);
@@ -5146,6 +5205,13 @@ module ts {
51465205

51475206
function inferFromSignature(source: Signature, target: Signature) {
51485207
forEachMatchingParameterType(source, target, inferFromTypes);
5208+
if (source.typePredicate &&
5209+
target.typePredicate &&
5210+
target.typePredicate.parameterIndex === source.typePredicate.parameterIndex) {
5211+
5212+
inferFromTypes(source.typePredicate.type, target.typePredicate.type);
5213+
return;
5214+
}
51495215
inferFromTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target));
51505216
}
51515217

@@ -5527,7 +5593,7 @@ module ts {
55275593
let targetType: Type;
55285594
let prototypeProperty = getPropertyOfType(rightType, "prototype");
55295595
if (prototypeProperty) {
5530-
// Target type is type of the protoype property
5596+
// Target type is type of the prototype property
55315597
let prototypePropertyType = getTypeOfSymbol(prototypeProperty);
55325598
if (prototypePropertyType !== anyType) {
55335599
targetType = prototypePropertyType;
@@ -5543,7 +5609,6 @@ module ts {
55435609
else if (rightType.flags & TypeFlags.Anonymous) {
55445610
constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
55455611
}
5546-
55475612
if (constructSignatures && constructSignatures.length) {
55485613
targetType = getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature))));
55495614
}
@@ -5563,10 +5628,38 @@ module ts {
55635628
return type;
55645629
}
55655630

5631+
function narrowTypeByTypePredicate(type: Type, expr: CallExpression, assumeTrue: boolean): Type {
5632+
if (type.flags & TypeFlags.Any) {
5633+
return type;
5634+
}
5635+
let signature = getResolvedSignature(expr);
5636+
if (!assumeTrue) {
5637+
if (type.flags & TypeFlags.Union && signature.typePredicate) {
5638+
return getUnionType(filter((<UnionType>type).types, t => !isTypeSubtypeOf(t, signature.typePredicate.type)));
5639+
}
5640+
return type;
5641+
}
5642+
if (signature.typePredicate) {
5643+
if (expr.arguments && expr.arguments[signature.typePredicate.parameterIndex]) {
5644+
if (getSymbolAtLocation(expr.arguments[signature.typePredicate.parameterIndex]) === symbol) {
5645+
if (isTypeSubtypeOf(signature.typePredicate.type, type)) {
5646+
return signature.typePredicate.type;
5647+
}
5648+
if (type.flags & TypeFlags.Union) {
5649+
return getUnionType(filter((<UnionType>type).types, t => isTypeSubtypeOf(t, signature.typePredicate.type)));
5650+
}
5651+
}
5652+
}
5653+
}
5654+
return type;
5655+
}
5656+
55665657
// Narrow the given type based on the given expression having the assumed boolean value. The returned type
55675658
// will be a subtype or the same type as the argument.
55685659
function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type {
55695660
switch (expr.kind) {
5661+
case SyntaxKind.CallExpression:
5662+
return narrowTypeByTypePredicate(type, <CallExpression>expr, assumeTrue);
55705663
case SyntaxKind.ParenthesizedExpression:
55715664
return narrowType(type, (<ParenthesizedExpression>expr).expression, assumeTrue);
55725665
case SyntaxKind.BinaryExpression:
@@ -8468,6 +8561,20 @@ module ts {
84688561
node.kind === SyntaxKind.FunctionExpression;
84698562
}
84708563

8564+
function getTypePredicateParameterIndex(parameterList: NodeArray<ParameterDeclaration>, parameter: Identifier): number {
8565+
let index = -1;
8566+
if (parameterList) {
8567+
for (let i = 0; i < parameterList.length; i++) {
8568+
let param = parameterList[i];
8569+
if (param.name.kind === SyntaxKind.Identifier &&
8570+
(<Identifier>param.name).text === parameter.text) {
8571+
8572+
return i;
8573+
}
8574+
}
8575+
}
8576+
}
8577+
84718578
function checkSignatureDeclaration(node: SignatureDeclaration) {
84728579
// Grammar checking
84738580
if (node.kind === SyntaxKind.IndexSignature) {
@@ -8488,6 +8595,27 @@ module ts {
84888595
checkSourceElement(node.type);
84898596
}
84908597

8598+
if (node.typePredicate) {
8599+
let links = getNodeLinks(node.typePredicate);
8600+
if (links.typePredicateParameterIndex === undefined) {
8601+
links.typePredicateParameterIndex = getTypePredicateParameterIndex(node.parameters, node.typePredicate.parameterName);
8602+
}
8603+
if (!links.typeFromTypePredicate) {
8604+
links.typeFromTypePredicate = getTypeFromTypeNode(node.typePredicate.type);
8605+
}
8606+
if (links.typePredicateParameterIndex >= 0) {
8607+
checkTypeAssignableTo(
8608+
links.typeFromTypePredicate,
8609+
getTypeAtLocation(node.parameters[links.typePredicateParameterIndex]),
8610+
node.typePredicate.type);
8611+
}
8612+
else if(node.typePredicate.parameterName) {
8613+
error(node.typePredicate.parameterName,
8614+
Diagnostics.Cannot_find_parameter_0,
8615+
node.typePredicate.parameterName.text);
8616+
}
8617+
}
8618+
84918619
if (produceDiagnostics) {
84928620
checkCollisionWithArgumentsInGeneratedCode(node);
84938621
if (compilerOptions.noImplicitAny && !node.type) {
@@ -10047,9 +10175,6 @@ module ts {
1004710175
if (node.expression) {
1004810176
let func = getContainingFunction(node);
1004910177
if (func) {
10050-
let returnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func));
10051-
let exprType = checkExpressionCached(node.expression);
10052-
1005310178
if (func.asteriskToken) {
1005410179
// A generator does not need its return expressions checked against its return type.
1005510180
// Instead, the yield expressions are checked against the element type.
@@ -10058,6 +10183,13 @@ module ts {
1005810183
return;
1005910184
}
1006010185

10186+
let signature = getSignatureFromDeclaration(func);
10187+
let exprType = checkExpressionCached(node.expression);
10188+
if (signature.typePredicate && exprType !== booleanType) {
10189+
error(node.expression, Diagnostics.A_type_guard_function_can_only_return_a_boolean);
10190+
}
10191+
let returnType = getReturnTypeOfSignature(signature);
10192+
1006110193
if (func.kind === SyntaxKind.SetAccessor) {
1006210194
error(node.expression, Diagnostics.Setters_cannot_return_a_value);
1006310195
}

src/compiler/diagnosticInformationMap.generated.ts

+5
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@ module ts {
179179
Generators_are_not_allowed_in_an_ambient_context: { code: 1221, category: DiagnosticCategory.Error, key: "Generators are not allowed in an ambient context." },
180180
An_overload_signature_cannot_be_declared_as_a_generator: { code: 1222, category: DiagnosticCategory.Error, key: "An overload signature cannot be declared as a generator." },
181181
_0_tag_already_specified: { code: 1223, category: DiagnosticCategory.Error, key: "'{0}' tag already specified." },
182+
A_non_type_guard_function_is_not_assignable_to_a_type_guard_function: { code: 1224, category: DiagnosticCategory.Error, key: "A non-type guard function is not assignable to a type guard function." },
183+
A_type_guard_function_can_only_return_a_boolean: { code: 1225, category: DiagnosticCategory.Error, key: "A type-guard function can only return a boolean." },
184+
Cannot_find_parameter_0: { code: 1226, category: DiagnosticCategory.Error, key: "Cannot find parameter '{0}'." },
185+
Type_guard_annotation_0_is_not_assignable_to_1: { code: 1227, category: DiagnosticCategory.Error, key: "Type-guard annotation '{0}' is not assignable to '{1}'." },
186+
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}'." },
182187
Duplicate_identifier_0: { code: 2300, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." },
183188
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." },
184189
Static_members_cannot_reference_class_type_parameters: { code: 2302, category: DiagnosticCategory.Error, key: "Static members cannot reference class type parameters." },

src/compiler/diagnosticMessages.json

+21
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,27 @@
703703
"category": "Error",
704704
"code": 1223
705705
},
706+
"A non-type guard function is not assignable to a type guard function.": {
707+
"category": "Error",
708+
"code": 1224
709+
},
710+
"A type-guard function can only return a boolean.": {
711+
"category": "Error",
712+
"code": 1225
713+
},
714+
"Cannot find parameter '{0}'.": {
715+
"category": "Error",
716+
"code": 1226
717+
},
718+
"Type-guard annotation '{0}' is not assignable to '{1}'.": {
719+
"category": "Error",
720+
"code": 1227
721+
},
722+
"Parameter index from '{0}' does not match the parameter index from '{1}'.": {
723+
"category": "Error",
724+
"code": 1228
725+
},
726+
706727

707728
"Duplicate identifier '{0}'.": {
708729
"category": "Error",

0 commit comments

Comments
 (0)