Skip to content

Sub-optimal type parameter inference with strictFunctionTypes enabled #52111

Closed
@jakebailey

Description

@jakebailey

These are cases I've extracted from #49929; they are cases where if you don't specify the type parameters yourself, the inference isn't good.

This is potentially a full duplicate of #49924, however, the fix suggested in that issue does not work properly in our codebase. To test these, it may be easiest to just clone #49929 with the explicit type parameters removed and see what works and what doesn't.

enum SyntaxKind {
    Block,
    Identifier,
    CaseClause,
    FunctionExpression,
    FunctionDeclaration,
}

interface Node { kind: SyntaxKind; }
interface Expression extends Node { _expressionBrand: any; }
interface Declaration extends Node { _declarationBrand: any; }
interface Block extends Node { kind: SyntaxKind.Block; }
interface Identifier extends Expression, Declaration { kind: SyntaxKind.Identifier; }
interface CaseClause extends Node { kind: SyntaxKind.CaseClause; }
interface FunctionDeclaration extends Declaration { kind: SyntaxKind.FunctionDeclaration; }

type HasLocals = Block | FunctionDeclaration;
declare function canHaveLocals(node: Node): node is HasLocals;

declare function assertNode<T extends Node, U extends T>(node: T | undefined, test: (node: T) => node is U): asserts node is U;
declare function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined): void;

function foo(node: FunctionDeclaration | CaseClause) {
    assertNode(node, canHaveLocals)

    node;
    // ^?

    assertNode<Node, HasLocals>(node, canHaveLocals)

    node;
    // ^?
}


declare function isExpression(node: Node): node is Expression;

declare function cast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut;

function bar(node: Identifier | FunctionDeclaration) {
    const a = cast(node, isExpression);
    //    ^?

    const b = cast<Expression>(node, isExpression);
    //    ^?
}
Output
"use strict";
var SyntaxKind;
(function (SyntaxKind) {
    SyntaxKind[SyntaxKind["Block"] = 0] = "Block";
    SyntaxKind[SyntaxKind["Identifier"] = 1] = "Identifier";
    SyntaxKind[SyntaxKind["CaseClause"] = 2] = "CaseClause";
    SyntaxKind[SyntaxKind["FunctionExpression"] = 3] = "FunctionExpression";
    SyntaxKind[SyntaxKind["FunctionDeclaration"] = 4] = "FunctionDeclaration";
})(SyntaxKind || (SyntaxKind = {}));
function foo(node) {
    assertNode(node, canHaveLocals);
    node;
    // ^?
    assertNode(node, canHaveLocals);
    node;
    // ^?
}
function bar(node) {
    const a = cast(node, isExpression);
    //    ^?
    const b = cast(node, isExpression);
    //    ^?
}
Compiler Options
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2017",
    "jsx": "react",
    "module": "ESNext",
    "moduleResolution": "node"
  }
}

Playground Link: Provided

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions