Skip to content
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

Compute flow types for function declarations #58411

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +29396 to 29397
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbf... if this is something the plan would like to pull in, we probably should enable narrowing on just anything. asserts make it possible to expand all non-nullable types

if (localOrExportSymbol.flags & SymbolFlags.Variable) {
if (localOrExportSymbol.flags & (SymbolFlags.Variable | SymbolFlags.Function)) {
if (assignmentKind === AssignmentKind.Definite) {
return isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(type) : type;
}
Expand Down Expand Up @@ -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 ||
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T extends (...args: never) => 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))

97 changes: 97 additions & 0 deletions tests/baselines/reference/assertionTypePredicatesOnFunction1.types
Original file line number Diff line number Diff line change
@@ -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<T extends (...args: never) => unknown>(
>assignLabel : <T extends (...args: never) => 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 : <T extends (...args: never) => 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 : <T extends (...args: never) => 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 : <T extends (...args: never) => unknown>(fn: T, label: string) => asserts fn is T & LabelledFunction
> : ^ ^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>c : () => void
> : ^^^^^^^^^^
>"c" : "c"
> : ^^^

c.label;
>c.label : string
> : ^^^^^^
>c : (() => void) & LabelledFunction
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>label : string
> : ^^^^^^

Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ 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?
uncalledFunctionChecksInConditional.ts(44,16): error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
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;
Expand All @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ if (isFoo || isFoo()) {
> : ^^^^^^^^^^^^^
>isFoo : () => boolean
> : ^^^^^^^^^^^^^
>isFoo() : boolean
> : ^^^^^^^
>isFoo : () => boolean
> : ^^^^^^^^^^^^^
>isFoo() : any
> : ^^^
>isFoo : never
> : ^^^^^

// error on isFoo
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// @strict: true
// @noEmit: true

// https://github.com/microsoft/TypeScript/issues/41232

interface LabelledFunction {
label: string;
}

declare function assignLabel<T extends (...args: never) => 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;