镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Destructuring array result of function with generics call sometimes lead to type loss #32003

Description

Hi!

TypeScript Version: 3.5.2

Search Terms: destructuring result function generics type loss any

Problem is only with snippet 2, when I get the third parameter with destructuring, the first one became any instead of the generic type. The problem doesn't show if the generic is explicitly provided. Snippets 1, 3 and 4 works as expected.

Code

function foo<T = {}>(): [T, string, number] {
	let a: T;
	return [a, 'foo', 2];
}

// 1
const [a, b] = foo();
// a: {}, b: string

// 2
const [x, y, z] = foo();
// x: any, y: string, z: number

// 3
const res = foo();
const [x2, y2, z2] = res;
// x2: {}, y: string, z: number

// 4
const [x3, y3, z3] = foo<{ bar: string }>();
// x3: { bar: string }, y3: string, z: number

Expected behavior:
x type is {}

Actual behavior:
x type is any

Playground Link: https://www.typescriptlang.org/play/#src=%0D%0Afunction%20foo%3CT%20%3D%20%7B%7D%3E()%3A%20%5BT%2C%20string%2C%20number%5D%20%7B%0D%0A%09let%20a%3A%20T%3B%0D%0A%09return%20%5Ba%2C%20'foo'%2C%202%5D%3B%0D%0A%7D%0D%0A%0D%0Aconst%20%5Ba%2C%20b%5D%20%3D%20foo()%3B%0D%0A%2F%2F%20a%3A%20%7B%7D%2C%20b%3A%20string%0D%0A%0D%0Aconst%20%5Bx%2C%20y%2C%20z%5D%20%3D%20foo()%3B%0D%0A%2F%2F%20x%3A%20any%2C%20y%3A%20string%2C%20z%3A%20number%0D%0A%0D%0Aconst%20res%20%3D%20foo()%3B%0D%0Aconst%20%5Bx2%2C%20y2%2C%20z2%5D%20%3D%20res%3B%0D%0A%2F%2F%20x2%3A%20%7B%7D%2C%20y%3A%20string%2C%20z%3A%20number%0D%0A%0D%0Aconst%20%5Bx3%2C%20y3%2C%20z3%5D%20%3D%20foo%3C%7B%20bar%3A%20string%20%7D%3E()%3B%0D%0A%2F%2F%20x3%3A%20%7B%20bar%3A%20string%20%7D%2C%20y3%3A%20string%2C%20z%3A%20number

Related Issues:
Some issues are covering topics that seems related, but I couldn't find one with my specific issue, sorry if there's already a duplicate for this one!

Thanks :)

Activity

  1. ahejlsberg commented on Jun 21, 2019

    @ahejlsberg
    Member

    This one is a bit strange. In examples 1 and 2, the call to foo() is contextually typed by the implied types of the array destrucuring patterns ([any, any] and [any, any, any] respectively). Since foo is generic and we have a contextual type, we perform generic return type inference (#16072). In example 2 where the arities of the tuple types match we then infer any for T, and since there is nothing else to make inferences from, that's what we end up with. In the other examples, we make no inferences for T at all and therefore end up with the default {}.

    Ideally we shouldn't make any inferences from the implied types of the destructuring patterns, but in other scenarios we do want a contextual type of a tuple type to indicate that array literals should be treated as tuples. So we have conflicting goals and there isn't an obvious simple fix. Add to that the anti-pattern of having a type parameter that isn't used in anywhere in the parameter list (it's basically just a disguised type assertion) and I think we'll call this a design limitation.

  2. miloszpp commented on Aug 22, 2019

    @miloszpp
    Contributor

    Just wanted to point out that this issue impacts typing hooks in React.

    const [pill, setPill] = useState();

    The type of pill is inferred to any while it should be undefined according to the type definition:

    function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
  3. danieloprado commented on Sep 10, 2019

    @danieloprado

    A little workaround:

    function foo<T = {}>(): [T, string, number, undefined] { // <~ add a 4th return
    	let a: T;
    	return [a, 'foo', 2, undefined]; // <~ return the 4th value as undefined
    }
    
    // 1
    const [a, b] = foo();
    // a: {}, b: string
    
    // 2
    const [x, y, z] = foo();
    // x:{}, y: string, z: number 👍
    
    // 3
    const res = foo();
    const [x2, y2, z2] = res;
    // x2: {}, y: string, z: number
    
    // 4
    const [x3, y3, z3] = foo<{ bar: string }>();
    // x3: { bar: string }, y3: string, z: number
  4. TLadd commented on Feb 6, 2020

    @TLadd

    Ran into this as well:

    export interface Sizes {
      width: number | null;
      height: number | null;
    }
    
      declare function useResizeAware<S = Sizes>(
        customReporter?: (target: HTMLElement | null) => S
      ): [string, S];
    
    
    const [a, b] = useResizeAware();
    
    const c = useResizeAware();
    const d = c[1]
    

    So in my case the generic does appear in the arguments but it is an optional parameter. d is properly typed as Sizes while b is of type any.

    Using overrides is another solution that fixed this for me:

      declare function useResizeAware(): [string, Sizes];
      declare function useResizeAware<S>(
        customReporter: (target: HTMLElement | null) => S
      ): [string, S];
    
  5. luokuning commented on Mar 25, 2020

    @luokuning

    Looks like TypeScript v3.8 have fixed that issue?

  6. mrblackus commented on Jun 24, 2020

    @mrblackus
    Author

    In can confirm that issue is fixed in both TS 3.8 and 3.9. Thanks!

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

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions