diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5a33748fb0b..80477ea1841 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3231,7 +3231,10 @@ module ts { let returnType: Type; let typePredicate: TypePredicate; - if (declaration.typePredicate) { + if (classType) { + returnType = classType; + } + else if (declaration.typePredicate) { returnType = booleanType; let typePredicateNode = declaration.typePredicate; let links = getNodeLinks(typePredicateNode); @@ -3242,14 +3245,11 @@ module ts { links.typeFromTypePredicate = getTypeFromTypeNode(declaration.typePredicate.type); } typePredicate = { - parameterName: typePredicateNode.parameterName ? typePredicateNode.parameterName.text : undefined, - parameterIndex: typePredicateNode.parameterName ? links.typePredicateParameterIndex : undefined, - type: links.typeFromTypePredicate + parameterName: typePredicateNode.parameterName ? typePredicateNode.parameterName.text : undefined, + parameterIndex: typePredicateNode.parameterName ? links.typePredicateParameterIndex : undefined, + type: links.typeFromTypePredicate }; } - else if (classType) { - returnType = classType; - } else if (declaration.type) { returnType = getTypeFromTypeNode(declaration.type); } @@ -3976,17 +3976,22 @@ module ts { function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature { let freshTypeParameters: TypeParameter[]; + let freshTypePredicate: TypePredicate; if (signature.typeParameters && !eraseTypeParameters) { freshTypeParameters = instantiateList(signature.typeParameters, mapper, instantiateTypeParameter); mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper); } if (signature.typePredicate) { - signature.typePredicate.type = instantiateType(signature.typePredicate.type, mapper); + freshTypePredicate = { + parameterName: signature.typePredicate.parameterName, + parameterIndex: signature.typePredicate.parameterIndex, + type: instantiateType(signature.typePredicate.type, mapper) + } } let result = createSignature(signature.declaration, freshTypeParameters, instantiateList(signature.parameters, mapper, instantiateSymbol), signature.resolvedReturnType ? instantiateType(signature.resolvedReturnType, mapper) : undefined, - signature.typePredicate, + freshTypePredicate, signature.minArgumentCount, signature.hasRestParameter, signature.hasStringLiterals); result.target = signature; result.mapper = mapper; @@ -4656,33 +4661,33 @@ module ts { } if (source.typePredicate && target.typePredicate) { - if (source.typePredicate.parameterIndex !== target.typePredicate.parameterIndex || - source.typePredicate.type.symbol !== target.typePredicate.type.symbol) { - + let hasDifferentParamaterIndex = source.typePredicate.parameterIndex !== target.typePredicate.parameterIndex; + let hasDifferentTypes = !isTypeIdenticalTo(source.typePredicate.type, target.typePredicate.type); + if (hasDifferentParamaterIndex || hasDifferentTypes) { if (reportErrors) { let sourceParamText = source.typePredicate.parameterName; let targetParamText = target.typePredicate.parameterName; let sourceTypeText = typeToString(source.typePredicate.type); let targetTypeText = typeToString(target.typePredicate.type); - if (source.typePredicate.parameterIndex !== target.typePredicate.parameterIndex) { - reportError(Diagnostics.Parameter_index_from_0_does_not_match_the_parameter_index_from_1, + if (hasDifferentParamaterIndex) { + reportError(Diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, sourceParamText, targetParamText); } - if (source.typePredicate.type.symbol !== target.typePredicate.type.symbol) { + if (hasDifferentTypes) { reportError(Diagnostics.Type_0_is_not_assignable_to_type_1, sourceTypeText, targetTypeText); } - reportError(Diagnostics.Type_guard_annotation_0_is_not_assignable_to_1, + reportError(Diagnostics.Type_predicate_0_is_not_assignable_to_1, `${sourceParamText} is ${sourceTypeText}`, `${targetParamText} is ${targetTypeText}`); } - return Ternary.False; } + return Ternary.True; } else if (!source.typePredicate && target.typePredicate) { if (reportErrors) { @@ -5223,12 +5228,15 @@ module ts { function inferFromSignature(source: Signature, target: Signature) { forEachMatchingParameterType(source, target, inferFromTypes); - if (source.typePredicate && - target.typePredicate && - target.typePredicate.parameterIndex === source.typePredicate.parameterIndex) { - - inferFromTypes(source.typePredicate.type, target.typePredicate.type); - return; + if (source.typePredicate && target.typePredicate) { + if (target.typePredicate.parameterIndex === source.typePredicate.parameterIndex) { + // Return types from type predicates are treated as booleans. In order to infer types + // from type predicates we would need infer from the type from type predicates. Since + // we can't infer any type information from the return types. We can just add a return + // statement after the below infer statement. + inferFromTypes(source.typePredicate.type, target.typePredicate.type); + return; + } } inferFromTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); } @@ -5633,19 +5641,24 @@ module ts { } if (targetType) { - // Narrow to the target type if it's a subtype of the current type - if (isTypeSubtypeOf(targetType, type)) { - return targetType; - } - // If the current type is a union type, remove all constituents that aren't subtypes of the target. - if (type.flags & TypeFlags.Union) { - return getUnionType(filter((type).types, t => isTypeSubtypeOf(t, targetType))); - } + return getOptionalNarrowedType(type, targetType); } return type; } + function getOptionalNarrowedType(originalType: Type, narrowedTypeCandidate: Type) { + // Narrow to the target type if it's a subtype of the current type + if (isTypeSubtypeOf(narrowedTypeCandidate, originalType)) { + return narrowedTypeCandidate; + } + // If the current type is a union type, remove all constituents that aren't subtypes of the target. + if (originalType.flags & TypeFlags.Union) { + return getUnionType(filter((originalType).types, t => isTypeSubtypeOf(t, narrowedTypeCandidate))); + } + return originalType; + } + function shouldNarrowTypeByTypePredicate(signature: Signature, expr: CallExpression): boolean { if (!signature.typePredicate) { return false; @@ -5670,18 +5683,16 @@ module ts { } let signature = getResolvedSignature(expr); if (!assumeTrue) { - if (type.flags & TypeFlags.Union && signature.typePredicate) { + if (type.flags & TypeFlags.Union && + signature.typePredicate && + getSymbolAtLocation(expr.arguments[signature.typePredicate.parameterIndex]) === symbol) { + return getUnionType(filter((type).types, t => !isTypeSubtypeOf(t, signature.typePredicate.type))); } return type; } if (shouldNarrowTypeByTypePredicate(signature, expr)) { - if (isTypeSubtypeOf(signature.typePredicate.type, type)) { - return signature.typePredicate.type; - } - if (type.flags & TypeFlags.Union) { - return getUnionType(filter((type).types, t => isTypeSubtypeOf(t, signature.typePredicate.type))); - } + return getOptionalNarrowedType(type, signature.typePredicate.type); } return type; } @@ -8640,7 +8651,7 @@ module ts { getTypeAtLocation(node.parameters[links.typePredicateParameterIndex]), node.typePredicate.type); } - else if(node.typePredicate.parameterName) { + else if (node.typePredicate.parameterName) { error(node.typePredicate.parameterName, Diagnostics.Cannot_find_parameter_0, node.typePredicate.parameterName.text); @@ -10217,6 +10228,9 @@ module ts { if (node.expression) { let func = getContainingFunction(node); if (func) { + let signature = getSignatureFromDeclaration(func); + let exprType = checkExpressionCached(node.expression); + if (func.asteriskToken) { // A generator does not need its return expressions checked against its return type. // Instead, the yield expressions are checked against the element type. @@ -10225,13 +10239,7 @@ module ts { return; } - let signature = getSignatureFromDeclaration(func); - let exprType = checkExpressionCached(node.expression); - if (signature.typePredicate && exprType !== booleanType) { - error(node.expression, Diagnostics.A_type_guard_function_can_only_return_a_boolean); - } let returnType = getReturnTypeOfSignature(signature); - if (func.kind === SyntaxKind.SetAccessor) { error(node.expression, Diagnostics.Setters_cannot_return_a_value); } @@ -10240,7 +10248,7 @@ module ts { error(node.expression, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); } } - else if (func.type || isGetAccessorWithAnnotatatedSetAccessor(func)) { + else if (func.type || isGetAccessorWithAnnotatatedSetAccessor(func) || signature.typePredicate) { checkTypeAssignableTo(exprType, returnType, node.expression, /*headMessage*/ undefined); } } diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index aba138b9a1b..46c1cd37b3f 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -182,8 +182,8 @@ module ts { 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." }, A_type_guard_function_can_only_return_a_boolean: { code: 1225, category: DiagnosticCategory.Error, key: "A type-guard function can only return a boolean." }, 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_predicate_0_is_not_assignable_to_1: { code: 1227, category: DiagnosticCategory.Error, key: "Type predicate '{0}' is not assignable to '{1}'." }, + Parameter_0_is_not_in_the_same_position_as_parameter_1: { code: 1228, category: DiagnosticCategory.Error, key: "Parameter '{0}' is not in the same position as parameter '{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." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index a94839fba7d..ebcba6386ff 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -715,11 +715,11 @@ "category": "Error", "code": 1226 }, - "Type-guard annotation '{0}' is not assignable to '{1}'.": { + "Type predicate '{0}' is not assignable to '{1}'.": { "category": "Error", "code": 1227 }, - "Parameter index from '{0}' does not match the parameter index from '{1}'.": { + "Parameter '{0}' is not in the same position as parameter '{1}'.": { "category": "Error", "code": 1228 }, diff --git a/tests/baselines/reference/typeGuardFunctionErrors.errors.txt b/tests/baselines/reference/typeGuardFunctionErrors.errors.txt index 8b5ed551dff..001fe8a14ae 100644 --- a/tests/baselines/reference/typeGuardFunctionErrors.errors.txt +++ b/tests/baselines/reference/typeGuardFunctionErrors.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(15,12): error TS1225: A type-guard function can only return a boolean. +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(18,55): error TS2304: Cannot find name 'x'. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(18,57): error TS1144: '{' or ';' expected. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(18,57): error TS2304: Cannot find name 'is'. @@ -17,13 +17,13 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(56,7): tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(61,7): error TS2339: Property 'propB' does not exist on type 'A'. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(66,7): error TS2339: Property 'propB' does not exist on type 'A'. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(71,46): error TS2345: Argument of type '(p1: any) => boolean' is not assignable to parameter of type '(p1: any) => boolean'. - Type-guard annotation 'p1 is C' is not assignable to 'p1 is B'. + Type predicate 'p1 is C' is not assignable to 'p1 is B'. Type 'C' is not assignable to type 'B'. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(75,1): error TS2322: Type '(p1: any, p2: any) => boolean' is not assignable to type '(p1: any, p2: any) => boolean'. A non-type guard function is not assignable to a type guard function. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(81,1): error TS2322: Type '(p1: any, p2: any) => boolean' is not assignable to type '(p1: any, p2: any) => boolean'. - Type-guard annotation 'p2 is A' is not assignable to 'p1 is A'. - Parameter index from 'p2' does not match the parameter index from 'p1'. + Type predicate 'p2 is A' is not assignable to 'p1 is A'. + Parameter 'p2' is not in the same position as parameter 'p1'. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(87,1): error TS2322: Type '(p1: any, p2: any, p3: any) => boolean' is not assignable to type '(p1: any, p2: any) => boolean'. tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(92,56): error TS1226: Cannot find parameter 'p1'. @@ -45,7 +45,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(92,56) function hasANonBooleanReturnStatement(x): x is A { return ''; ~~ -!!! error TS1225: A type-guard function can only return a boolean. +!!! error TS2322: Type 'string' is not assignable to type 'boolean'. } function hasTypeGuardTypeInsideTypeGuardType(x): x is x is A { @@ -137,7 +137,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(92,56) acceptingDifferentSignatureTypeGuardFunction(isC); ~~~ !!! error TS2345: Argument of type '(p1: any) => boolean' is not assignable to parameter of type '(p1: any) => boolean'. -!!! error TS2345: Type-guard annotation 'p1 is C' is not assignable to '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'. // Boolean not assignable to type guard @@ -154,8 +154,8 @@ tests/cases/conformance/expressions/typeGuards/typeGuardFunctionErrors.ts(92,56) assign2 = function(p1, p2): p2 is A { ~~~~~~~ !!! error TS2322: Type '(p1: any, p2: any) => boolean' is not assignable to type '(p1: any, p2: any) => boolean'. -!!! error TS2322: Type-guard annotation 'p2 is A' is not assignable to 'p1 is A'. -!!! error TS2322: Parameter index from 'p2' does not match the parameter index from 'p1'. +!!! error TS2322: Type predicate 'p2 is A' is not assignable to 'p1 is A'. +!!! error TS2322: Parameter 'p2' is not in the same position as parameter 'p1'. return true; };