diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 7e9f3ced07f..081bb1fb5e9 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1094,7 +1094,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { inAssignmentPattern = saveInAssignmentPattern; return; } - if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && !options.allowUnreachableCode) { + if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && (!options.allowUnreachableCode || node.kind === SyntaxKind.ReturnStatement)) { (node as HasFlowNode).flowNode = currentFlow; } switch (node.kind) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6790bcc8f0f..095ba1a7b94 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15463,7 +15463,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { createTypePredicateFromTypePredicateNode(type, signature) : jsdocPredicate || noTypePredicate; } - else if (signature.declaration && isFunctionLikeDeclaration(signature.declaration) && (!signature.resolvedReturnType || signature.resolvedReturnType === booleanType)) { + else if (signature.declaration && isFunctionLikeDeclaration(signature.declaration) && (!signature.resolvedReturnType || signature.resolvedReturnType.flags & TypeFlags.Boolean)) { const { declaration } = signature; signature.resolvedTypePredicate = noTypePredicate; // avoid infinite loop signature.resolvedTypePredicate = getTypePredicateFromBody(declaration) || noTypePredicate; @@ -37411,7 +37411,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Only attempt to infer a type predicate if there's exactly one return. let singleReturn: Expression | undefined; - let singleReturnStatement: ReturnStatement | undefined; if (func.body && func.body.kind !== SyntaxKind.Block) { singleReturn = func.body; // arrow function } @@ -37420,7 +37419,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const bailedEarly = forEachReturnStatement(func.body as Block, returnStatement => { if (singleReturn || !returnStatement.expression) return true; - singleReturnStatement = returnStatement; singleReturn = returnStatement.expression; }); if (bailedEarly || !singleReturn) return undefined; @@ -37439,11 +37437,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkIfExpressionRefinesAnyParameter(expr: Expression): [number, Type] | undefined { expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); const type = checkExpressionCached(expr); - if (type !== booleanType) return undefined; + if (!(type.flags & TypeFlags.Boolean)) return undefined; return forEach(func.parameters, (param, i) => { - const initType = getSymbolLinks(param.symbol).type; - if (!initType || initType === booleanType || isSymbolAssigned(param.symbol)) { + const initType = getTypeOfSymbol(param.symbol); + if (!initType || initType.flags & TypeFlags.Boolean || isSymbolAssigned(param.symbol)) { // Refining "x: boolean" to "x is true" or "x is false" isn't useful. return; } @@ -37455,7 +37453,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function checkIfExpressionRefinesParameter(expr: Expression, param: ParameterDeclaration, initType: Type): Type | undefined { - const antecedent = (expr as Expression & { flowNode?: FlowNode; }).flowNode ?? { flags: FlowFlags.Start }; + const antecedent = (expr as Expression & { flowNode?: FlowNode; }).flowNode || + expr.parent.kind === SyntaxKind.ReturnStatement && (expr.parent as ReturnStatement).flowNode || + { flags: FlowFlags.Start }; const trueCondition: FlowCondition = { flags: FlowFlags.TrueCondition, node: expr, @@ -37472,19 +37472,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { flags: FlowFlags.FalseCondition, }; const falseSubtype = getFlowTypeOfReference(param.name, trueType, trueType, func, falseCondition); - if (!isTypeIdenticalTo(falseSubtype, neverType)) return undefined; - - // the parameter type may already have been narrowed due to an assertion. - // There's no precise way to represent an assertion that's also a predicate. Best not to try. - // We do this check last since it's unlikely to filter out many possible predicates. - if (singleReturnStatement?.flowNode) { - const typeAtReturn = getFlowTypeOfReference(param.name, initType, initType, func, singleReturnStatement?.flowNode); - if (typeAtReturn !== initType) { - return undefined; - } - } - - return trueType; + return falseSubtype.flags & TypeFlags.Never ? trueType : undefined; } } diff --git a/tests/baselines/reference/inferTypePredicates.errors.txt b/tests/baselines/reference/inferTypePredicates.errors.txt index c000e684e93..440b668c7ef 100644 --- a/tests/baselines/reference/inferTypePredicates.errors.txt +++ b/tests/baselines/reference/inferTypePredicates.errors.txt @@ -14,11 +14,9 @@ inferTypePredicates.ts(113,7): error TS2322: Type 'string | number' is not assig inferTypePredicates.ts(115,7): error TS2322: Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'. inferTypePredicates.ts(205,7): error TS2741: Property 'z' is missing in type 'C1' but required in type 'C2'. -inferTypePredicates.ts(252,7): error TS2322: Type 'string | number | Date' is not assignable to type 'string'. - Type 'number' is not assignable to type 'string'. -==== inferTypePredicates.ts (11 errors) ==== +==== inferTypePredicates.ts (10 errors) ==== // https://github.com/microsoft/TypeScript/issues/16069 const numsOrNull = [1, 2, 3, 4, null]; @@ -298,9 +296,6 @@ inferTypePredicates.ts(252,7): error TS2322: Type 'string | number | Date' is no declare let snd: string | number | Date; if (assertAndPredicate(snd)) { let t: string = snd; // should error - ~ -!!! error TS2322: Type 'string | number | Date' is not assignable to type 'string'. -!!! error TS2322: Type 'number' is not assignable to type 'string'. } function isNumberWithThis(this: Date, x: number | string) { diff --git a/tests/baselines/reference/inferTypePredicates.types b/tests/baselines/reference/inferTypePredicates.types index c964d9d329a..4564daa4388 100644 --- a/tests/baselines/reference/inferTypePredicates.types +++ b/tests/baselines/reference/inferTypePredicates.types @@ -886,7 +886,7 @@ if (isNumOrStr(unk)) { // A function can be a type predicate even if it throws. function assertAndPredicate(x: string | number | Date) { ->assertAndPredicate : (x: string | number | Date) => boolean +>assertAndPredicate : (x: string | number | Date) => x is string >x : string | number | Date if (x instanceof Date) { @@ -910,12 +910,12 @@ declare let snd: string | number | Date; if (assertAndPredicate(snd)) { >assertAndPredicate(snd) : boolean ->assertAndPredicate : (x: string | number | Date) => boolean +>assertAndPredicate : (x: string | number | Date) => x is string >snd : string | number | Date let t: string = snd; // should error >t : string ->snd : string | number | Date +>snd : string } function isNumberWithThis(this: Date, x: number | string) {