Skip to content

Issue deriving tuple types on overridden methodΒ #57387

Closed as not planned
Closed as not planned

Description

πŸ”Ž Search Terms

tuple type infer inference inheritance

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about tuples and type inference.

⏯ Playground Link

https://www.typescriptlang.org/play?alwaysStrict=false&target=2&jsx=0&module=5&ts=5.3.3&filetype=ts#code/CYUwxgNghgTiAEAXAngBwQZUTAlgOwHMAxHGAZ0QBUBXVCBAXngG0LdCAaeAOl6j2QBdANwAoUZChky8IgHs58AN6j4a+GXRgAFAEoAXPCztipCjToIV6m-DiJqMPCwBEACxAQIcl4NXqAX1EgiWhpeAAhWHgQAA9EEDxgGXlFa3VNcD1lf1s7EAcnVwIFYAAjZBAXLiUyOQBbArd8AkMXTzIqgL8bIICgA

πŸ’» Code

declare type StringFirstTuple = [string, ...any];

class Foo {
    spec(): StringFirstTuple {
        return ["hello"]
    }
}

class Bar extends Foo {
    spec() {
        return ["goodbye", {something: "else"}]
    }
}

πŸ™ Actual behavior

The spec method on Bar failed to typecheck, despite unambiguously implementing the same type signature as spec on Foo.

Annotating Bar's spec method to return a StringFirstTuple typechecks properly.

The error message seems to indicate that the return type of spec in Bar has been generalized to Array<string | {something: string}> before checking the overridden type.

πŸ™‚ Expected behavior

A function that returns an array that is hard-coded to match a tuple type should be substitutable for a function that is specified to return that tuple type.

Additional information about the issue

No response

Activity

  1. MartinJohns commented on Feb 12, 2024

    @MartinJohns
    Contributor

    Methods don't inherit the types of the methods they're overriding, so the type of your Bar.spec method is inferred from the return value. #32082 matches this the most.

  2. pvande commented on Feb 12, 2024

    @pvande
    Author

    I suppose that's fair, though from an ergonomics perspective it feels odd to me that TS would mark a type mismatch between Bar.spec and Foo.spec, but never actually check whether Bar.spec and Foo.spec could be described equivalently.

    In a TypeScript-forward project, where relying on implicit types is generally discouraged, the current behavior may be completely reasonable; I'm running into this in a Javascript project with JSDoc types, and the lack of inference is an unfortunate hurdle for most consumers (especially as the tuple type gets pushed deeper inside a nested object).

  3. RyanCavanaugh commented on Feb 12, 2024

    @RyanCavanaugh
    Member

    You can't really remove this inconsistency, only change where it appears. If you had written this, for example, m is unambiguously inferred as string[] and the code still fails

    class Bar extends Foo {
        spec() {
            const m = ["goodbye", {something: "else"}]
            return m;
        }
    }
  4. pvande commented on Feb 12, 2024

    @pvande
    Author

    Ryan Cavanaugh (@RyanCavanaugh) I'm not sure I follow your argument. In your example, m will always be inferred as Array<string | {something: string}> (unless I'm really missing something), and no, neither string[] nor Array<string | {something: string}> will satisfy [string, ...any].

    On the other hand, [string, ...any] describes an array of any length which has a string at index 0, which absolutely can be satisfied by a (edit: non-empty) string[]. And while it's similarly correct to say that ["goodbye", {something: "else}] is an Array<string | {something: string}>, it is also a [string, ...any] (and a [string, object], and many other things).

    To someone unfamiliar with the implementation of TypeScript, it definitely feels like a defect to say that a hard-coded value like ["goodbye"] is not a satisfactory "array that begins with a string" unless it is also explicitly described as such.

  5. MartinJohns commented on Feb 12, 2024

    @MartinJohns
    Contributor

    On the other hand, [string, ...any] describes an array of any length which has a string at index 0, which absolutely can be satisfied by a string[].

    Arrays can be empty, not having a string at index 0.

  6. pvande commented on Feb 12, 2024

    @pvande
    Author

    On the other hand, [string, ...any] describes an array of any length which has a string at index 0, which absolutely can be satisfied by a string[].

    Arrays can be empty, not having a string at index 0.

    Fair point.

  7. RyanCavanaugh commented on Feb 13, 2024

    @RyanCavanaugh
    Member

    Pieter van de Bruggen (@pvande) the vibes might be off, but the "fix" is that the method return type inference algorithm gets more complex (and very much more non-local in this case), and "the type system is too complicated to understand" is already a problem that people talk about. Everything can be made more complicated, but complexity itself is also understood to be a defect. It's not infrequent for people to claim that the inference system should be "worse" (less likely to understand implicit intent) for the sake of being "simpler". I'm not saying this is an absolute must-not-change, but it's also not a bug in the sense of producing unsound behavior. And even in the presence of a fix, m is also "wrong", so it's not like a fix here relieves you of the burden of understanding the concept that the inferred type of an expression might be wider than its tightest possible bound.

  8. pvande commented on Feb 13, 2024

    @pvande
    Author

    Ryan Cavanaugh (@RyanCavanaugh) Thanks for your patience with me about this.

    … the "fix" is that the method return type inference algorithm gets more complex…

    That does seem likely.

    It's not infrequent for people to claim that the inference system should be "worse" (less likely to understand implicit intent) for the sake of being "simpler".

    Not being a regular member in this community, that's not something I've been aware of, though it also seems reasonable.

    …it's also not a bug in the sense of producing unsound behavior.

    Completely agree. At worst, it's a spurious error in VS Code for folks leveraging a typed API in a typeless Javascript project.

    And even in the presence of a fix, m is also "wrong", so it's not like a fix here relieves you of the burden of understanding the concept that the inferred type of an expression might be wider than its tightest possible bound.

    I can imagine a world in which implicitly typed literals are lazily evaluated and "cast" at point-of-use. In such a world, all of the following seem reasonable:

    const value = ["string", {number: 2}, /s/]
    
    let a: [string, ...any] = value
    let b: [any, {number: number}, any] = value
    let c: [...any, RegExp] = value
    let d: Array<string | {number: number} | RegExp> = value
    
    function e(arg: [string, ...any]): void { arg }
    e(value)
    
    const f = () => value
    a = f()
    b = f()
    c = f()
    d = f()

    What I don't know offhand is what the practicalities and drawbacks of such an implementation are, though I'm confident they exist.

    That said, my only goal in opening this issue was to call attention to a sharp edge that my team encountered, to raise awareness and discussion.

  9. fatcerberus commented on Feb 14, 2024

    @fatcerberus

    I can imagine a world in which implicitly typed literals are lazily evaluated and "cast" at point-of-use. In such a world, all of the following seem reasonable:

    This is an interesting thought and not something I personally ever considered. One of the fundamental tradeoffs you make with a typed language is that you give up the ability to speak of individual values in favor of giving the compiler (and potentially, the programmer) an easier job by being able to reason about more general types (i.e. constraints) of values instead. But I could definitely imagine a world too where e.g. object and array literals (which might satisfy several different constraints thanks to contextual typing) have type inference "deferred" until they're actually assigned to something with an explicit type annotation.

    That said, deferral brings with it its own problems. See, e.g. conditional types in a generic context.

  10. typescript-bot commented on Feb 17, 2024

    @typescript-bot
    Contributor

    This issue has been marked as "Not a Defect" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  11. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions