From cdcf9172f168d3883c62001387843f8d40dbaa32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 3 May 2024 00:01:01 +0200 Subject: [PATCH] Compute flow types for function declarations --- src/compiler/checker.ts | 6 +- ...assertionTypePredicatesOnFunction1.symbols | 65 +++++++++++++ .../assertionTypePredicatesOnFunction1.types | 97 +++++++++++++++++++ ...lledFunctionChecksInConditional.errors.txt | 7 +- .../uncalledFunctionChecksInConditional.types | 8 +- .../assertionTypePredicatesOnFunction1.ts | 25 +++++ 6 files changed, 200 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/assertionTypePredicatesOnFunction1.symbols create mode 100644 tests/baselines/reference/assertionTypePredicatesOnFunction1.types create mode 100644 tests/cases/conformance/controlFlow/assertionTypePredicatesOnFunction1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 027007fb15b62..d0a4caacbaca4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29393,9 +29393,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias; - // We only narrow variables and parameters occurring in a non-assignment position. For all other + // We only narrow variables, parameters and functions occurring in a non-assignment position. For all other // entities we simply return the declared type. - if (localOrExportSymbol.flags & SymbolFlags.Variable) { + if (localOrExportSymbol.flags & (SymbolFlags.Variable | SymbolFlags.Function)) { if (assignmentKind === AssignmentKind.Definite) { return isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(type) : type; } @@ -29443,7 +29443,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // We only look for uninitialized variables in strict null checking mode, and only when we can analyze // the entire control flow graph from the variable's declaration (i.e. when the flow container and // declaration container are the same). - const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isSameScopedBindingElement(node, declaration) || + const assumeInitialized = localOrExportSymbol.flags & SymbolFlags.Function || isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isSameScopedBindingElement(node, declaration) || type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 || isInTypeQuery(node) || isInAmbientOrTypeNode(node) || node.parent.kind === SyntaxKind.ExportSpecifier) || node.parent.kind === SyntaxKind.NonNullExpression || diff --git a/tests/baselines/reference/assertionTypePredicatesOnFunction1.symbols b/tests/baselines/reference/assertionTypePredicatesOnFunction1.symbols new file mode 100644 index 0000000000000..064fc47593193 --- /dev/null +++ b/tests/baselines/reference/assertionTypePredicatesOnFunction1.symbols @@ -0,0 +1,65 @@ +//// [tests/cases/conformance/controlFlow/assertionTypePredicatesOnFunction1.ts] //// + +=== assertionTypePredicatesOnFunction1.ts === +// https://github.com/microsoft/TypeScript/issues/41232 + +interface LabelledFunction { +>LabelledFunction : Symbol(LabelledFunction, Decl(assertionTypePredicatesOnFunction1.ts, 0, 0)) + + label: string; +>label : Symbol(LabelledFunction.label, Decl(assertionTypePredicatesOnFunction1.ts, 2, 28)) +} + +declare function assignLabel unknown>( +>assignLabel : Symbol(assignLabel, Decl(assertionTypePredicatesOnFunction1.ts, 4, 1)) +>T : Symbol(T, Decl(assertionTypePredicatesOnFunction1.ts, 6, 29)) +>args : Symbol(args, Decl(assertionTypePredicatesOnFunction1.ts, 6, 40)) + + fn: T, +>fn : Symbol(fn, Decl(assertionTypePredicatesOnFunction1.ts, 6, 68)) +>T : Symbol(T, Decl(assertionTypePredicatesOnFunction1.ts, 6, 29)) + + label: string, +>label : Symbol(label, Decl(assertionTypePredicatesOnFunction1.ts, 7, 8)) + +): asserts fn is T & LabelledFunction; +>fn : Symbol(fn, Decl(assertionTypePredicatesOnFunction1.ts, 6, 68)) +>T : Symbol(T, Decl(assertionTypePredicatesOnFunction1.ts, 6, 29)) +>LabelledFunction : Symbol(LabelledFunction, Decl(assertionTypePredicatesOnFunction1.ts, 0, 0)) + +function a() {} +>a : Symbol(a, Decl(assertionTypePredicatesOnFunction1.ts, 9, 38)) + +assignLabel(a, "a"); +>assignLabel : Symbol(assignLabel, Decl(assertionTypePredicatesOnFunction1.ts, 4, 1)) +>a : Symbol(a, Decl(assertionTypePredicatesOnFunction1.ts, 9, 38)) + +a.label; +>a.label : Symbol(LabelledFunction.label, Decl(assertionTypePredicatesOnFunction1.ts, 2, 28)) +>a : Symbol(a, Decl(assertionTypePredicatesOnFunction1.ts, 9, 38)) +>label : Symbol(LabelledFunction.label, Decl(assertionTypePredicatesOnFunction1.ts, 2, 28)) + +const b = function () {}; +>b : Symbol(b, Decl(assertionTypePredicatesOnFunction1.ts, 15, 5)) + +assignLabel(b, "b"); +>assignLabel : Symbol(assignLabel, Decl(assertionTypePredicatesOnFunction1.ts, 4, 1)) +>b : Symbol(b, Decl(assertionTypePredicatesOnFunction1.ts, 15, 5)) + +b.label; +>b.label : Symbol(LabelledFunction.label, Decl(assertionTypePredicatesOnFunction1.ts, 2, 28)) +>b : Symbol(b, Decl(assertionTypePredicatesOnFunction1.ts, 15, 5)) +>label : Symbol(LabelledFunction.label, Decl(assertionTypePredicatesOnFunction1.ts, 2, 28)) + +const c = () => {}; +>c : Symbol(c, Decl(assertionTypePredicatesOnFunction1.ts, 19, 5)) + +assignLabel(c, "c"); +>assignLabel : Symbol(assignLabel, Decl(assertionTypePredicatesOnFunction1.ts, 4, 1)) +>c : Symbol(c, Decl(assertionTypePredicatesOnFunction1.ts, 19, 5)) + +c.label; +>c.label : Symbol(LabelledFunction.label, Decl(assertionTypePredicatesOnFunction1.ts, 2, 28)) +>c : Symbol(c, Decl(assertionTypePredicatesOnFunction1.ts, 19, 5)) +>label : Symbol(LabelledFunction.label, Decl(assertionTypePredicatesOnFunction1.ts, 2, 28)) + diff --git a/tests/baselines/reference/assertionTypePredicatesOnFunction1.types b/tests/baselines/reference/assertionTypePredicatesOnFunction1.types new file mode 100644 index 0000000000000..154555f2efc02 --- /dev/null +++ b/tests/baselines/reference/assertionTypePredicatesOnFunction1.types @@ -0,0 +1,97 @@ +//// [tests/cases/conformance/controlFlow/assertionTypePredicatesOnFunction1.ts] //// + +=== assertionTypePredicatesOnFunction1.ts === +// https://github.com/microsoft/TypeScript/issues/41232 + +interface LabelledFunction { + label: string; +>label : string +> : ^^^^^^ +} + +declare function assignLabel unknown>( +>assignLabel : unknown>(fn: T, label: string) => asserts fn is T & LabelledFunction +> : ^ ^^^^^^^^^^^^^ ^^ ^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>args : never +> : ^^^^^ + + fn: T, +>fn : T +> : ^ + + label: string, +>label : string +> : ^^^^^^ + +): asserts fn is T & LabelledFunction; + +function a() {} +>a : () => void +> : ^^^^^^^^^^ + +assignLabel(a, "a"); +>assignLabel(a, "a") : void +> : ^^^^ +>assignLabel : unknown>(fn: T, label: string) => asserts fn is T & LabelledFunction +> : ^ ^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>a : () => void +> : ^^^^^^^^^^ +>"a" : "a" +> : ^^^ + +a.label; +>a.label : string +> : ^^^^^^ +>a : (() => void) & LabelledFunction +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>label : string +> : ^^^^^^ + +const b = function () {}; +>b : () => void +> : ^^^^^^^^^^ +>function () {} : () => void +> : ^^^^^^^^^^ + +assignLabel(b, "b"); +>assignLabel(b, "b") : void +> : ^^^^ +>assignLabel : unknown>(fn: T, label: string) => asserts fn is T & LabelledFunction +> : ^ ^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>b : () => void +> : ^^^^^^^^^^ +>"b" : "b" +> : ^^^ + +b.label; +>b.label : string +> : ^^^^^^ +>b : (() => void) & LabelledFunction +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>label : string +> : ^^^^^^ + +const c = () => {}; +>c : () => void +> : ^^^^^^^^^^ +>() => {} : () => void +> : ^^^^^^^^^^ + +assignLabel(c, "c"); +>assignLabel(c, "c") : void +> : ^^^^ +>assignLabel : unknown>(fn: T, label: string) => asserts fn is T & LabelledFunction +> : ^ ^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>c : () => void +> : ^^^^^^^^^^ +>"c" : "c" +> : ^^^ + +c.label; +>c.label : string +> : ^^^^^^ +>c : (() => void) & LabelledFunction +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>label : string +> : ^^^^^^ + diff --git a/tests/baselines/reference/uncalledFunctionChecksInConditional.errors.txt b/tests/baselines/reference/uncalledFunctionChecksInConditional.errors.txt index 4a57e071d84c8..8c3309661f5bd 100644 --- a/tests/baselines/reference/uncalledFunctionChecksInConditional.errors.txt +++ b/tests/baselines/reference/uncalledFunctionChecksInConditional.errors.txt @@ -2,6 +2,8 @@ uncalledFunctionChecksInConditional.ts(5,5): error TS2774: This condition will a uncalledFunctionChecksInConditional.ts(9,5): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? uncalledFunctionChecksInConditional.ts(9,14): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? uncalledFunctionChecksInConditional.ts(13,5): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? +uncalledFunctionChecksInConditional.ts(13,14): error TS2349: This expression is not callable. + Type 'never' has no call signatures. uncalledFunctionChecksInConditional.ts(32,10): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? uncalledFunctionChecksInConditional.ts(36,5): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? uncalledFunctionChecksInConditional.ts(40,22): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? @@ -9,7 +11,7 @@ uncalledFunctionChecksInConditional.ts(44,16): error TS2774: This condition will uncalledFunctionChecksInConditional.ts(48,22): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? -==== uncalledFunctionChecksInConditional.ts (9 errors) ==== +==== uncalledFunctionChecksInConditional.ts (10 errors) ==== declare function isFoo(): boolean; declare function isBar(): boolean; declare const isUndefinedFoo: (() => boolean) | undefined; @@ -31,6 +33,9 @@ uncalledFunctionChecksInConditional.ts(48,22): error TS2774: This condition will if (isFoo || isFoo()) { ~~~~~ !!! error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead? + ~~~~~ +!!! error TS2349: This expression is not callable. +!!! error TS2349: Type 'never' has no call signatures. // error on isFoo } diff --git a/tests/baselines/reference/uncalledFunctionChecksInConditional.types b/tests/baselines/reference/uncalledFunctionChecksInConditional.types index b9f874c7f4603..e446eb801084b 100644 --- a/tests/baselines/reference/uncalledFunctionChecksInConditional.types +++ b/tests/baselines/reference/uncalledFunctionChecksInConditional.types @@ -36,10 +36,10 @@ if (isFoo || isFoo()) { > : ^^^^^^^^^^^^^ >isFoo : () => boolean > : ^^^^^^^^^^^^^ ->isFoo() : boolean -> : ^^^^^^^ ->isFoo : () => boolean -> : ^^^^^^^^^^^^^ +>isFoo() : any +> : ^^^ +>isFoo : never +> : ^^^^^ // error on isFoo } diff --git a/tests/cases/conformance/controlFlow/assertionTypePredicatesOnFunction1.ts b/tests/cases/conformance/controlFlow/assertionTypePredicatesOnFunction1.ts new file mode 100644 index 0000000000000..fc441169574c5 --- /dev/null +++ b/tests/cases/conformance/controlFlow/assertionTypePredicatesOnFunction1.ts @@ -0,0 +1,25 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/41232 + +interface LabelledFunction { + label: string; +} + +declare function assignLabel unknown>( + fn: T, + label: string, +): asserts fn is T & LabelledFunction; + +function a() {} +assignLabel(a, "a"); +a.label; + +const b = function () {}; +assignLabel(b, "b"); +b.label; + +const c = () => {}; +assignLabel(c, "c"); +c.label;