Skip to content

Narrowing does not work for constrained template arguments in JS #38309

Closed

Description

@AlCalzone

TypeScript Version: 3.8.3

Search Terms: jsdoc template narrow

Code

/**
 * @template {string | string[]} T
 * @param {T} a 
 */
function foo(a) {
	if (Array.isArray(a)) {
		a.map; // somewhat correctly narrowed to (T & any[])
	} else {
		a.substr; // Error, not narrowed at all (T)
	}
}

Expected behavior:

  1. in the if-branch, a is narrowed to string[]
  2. in the else-branch, a is narrowed to string

Actual behavior:

  1. in the if-branch, a is narrowed to T & any[]
  2. in the else-branch, a is NOT narrowed

Playground Link: This is JS, so the playground doesn't work

Related Issues: Constraint support was added in #24600

Activity

  1. IllusionMH commented on May 4, 2020

    @IllusionMH
    Contributor

    It's not only in JS.

    TS nightly repro

  2. AlCalzone commented on May 7, 2020

    @AlCalzone
    ContributorAuthor

    You're right. That is probably intended then, but at the very least it is not intuitive...

  3. RyanCavanaugh commented on Jun 5, 2020

    @RyanCavanaugh
    Member

    Anders Hejlsberg (@ahejlsberg) Wesley Wigham (@weswigham) should we be narrowing to T & string in the else?

  4. weswigham commented on Oct 22, 2020

    @weswigham
    Member

    should we be narrowing to T & string in the else?

    🤷‍♀️ We certainly don't today. (Generics are never narrowed in the false condition.) I'd normally say that something like #29317 would allow us to fix this (since we would pretty trivially say T & not string[] in the false branch, which is more accurate, which can then go on to filter the constraint when fetched). Specifically in cases like this, where we're guarding against an element of a union constraint, filtering the constraint and intersecting it may work as a substitute for that... Unfortunately, specifically for Array, Array.isArray now has a return signature type of arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[], which is decidedly not "exactly an element of a union constraint", so it's.... unclear that we could adjust this without a full negated-type-style fix.

  5. added
    Design LimitationConstraints of the existing architecture prevent this from being fixed
    BlockedA fix for this issue is blocked on another fix being in place
    and removed
    Needs InvestigationThis issue needs a team member to investigate its status.
    on Oct 22, 2020
  6. 6 remaining items

  7. n9 commented on Jun 8, 2021

    @n9

    Is this issue also cause for the following error?

    let x: unknown
    if (!(x instanceof Array)) {
        x = []
    }
    x.length // <- Object is of type 'unknown'.

    TS Play

    Or the cause is different?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BlockedA fix for this issue is blocked on another fix being in placeDesign LimitationConstraints of the existing architecture prevent this from being fixedRescheduledThis issue was previously scheduled to an earlier milestone

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions