microsoft / microsoft/TypeScript

TypeScript 4.1+: Generic binding too broad in recursive conditional types

Đang mở
#41,380 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Needs Investigation
Ngôn ngữ chính
Go
Star
111k
Fork
14.4k
Merge trung bình
1 ngày 19 giờ
Pull request đã merge (30 ngày)
117

Mô tả

TypeScript Version: 4.2.0-dev.20201103

Search Terms: Recursive Conditional types, generics, tuple types

Code

// A test object
const obj = {
    a: '2',
    b: {
        c: 3,
        d: {
            e: 'string',
            f: {
                g: {
                    h: 4
                }
            }
        }
    }
} as const

// its type
type Struct = typeof obj;

/**
 * First recursive conditional type 
 * 
 * CheckArguments gets 
 * Obj - A nested object
 * Arugments - A tuple of keys to go down a nested path
 * 
 * The type is recursive, I check if the current argument lists extends keyof Obj --> Then the recursion ends
 * Otherwise, I check if the first value in the tuple is keyof Obj, infer the rest, and go down the same type
 * again
 */
type CheckArguments<Obj, Arguments> = 
    Arguments extends [keyof Obj] ? Obj[Arguments[number]] :
    Arguments extends [keyof Obj, ...infer U] ? CheckArguments<Exclude<Obj[keyof Obj], string | number>, U> : never;

/**
 * Tests, all 👍
 */
type Foo = CheckArguments<Struct, ['a']> // "2"
type Foo2 = CheckArguments<Struct, ['b', 'd', 'e']> // "string"
type Foo3 = CheckArguments<Struct, ['b', 'd', 'f', 'g', 'h']> // 4

/**
 * Second recursive conditional type
 * 
 * Arguments gets
 * Obj - A nested Obj
 * 
 * The recursive conditional type creates a union type of possible nested arguments in a tuple
 * This is based on Anders example from TSConf: https://github.com/ahejlsberg/tsconf2020-demos/blob/master/template/main.ts
 * (Dotted paths)
 */
type Arguments<Obj> = 
    Obj extends object ?
        [keyof Obj] | SubArguments<Obj, keyof Obj> :
        never;

// A helper type
type SubArguments<Obj, Key> =  Key extends keyof Obj ? [Key, ...Arguments<Obj[Key]>] : never;

// For example, the possible tuples of Struct 👍
type Bar = Arguments<Struct>;

// equals to this union type
type Bar2 = ["a" | "b"] | ["b", "c" | "d"] | ["b", "d", "e" | "f"] | ["b", "d", "f", "g"] | ["b", "d", "f", "g", "h"]

/**
 * So both recursive conditional types work on their own. A problem is once I want to combine
 * them in a function, where I expect the first argument to bind to a value type within the Arguments union
 * 
 * Instead of having just one value type passed to CheckArguments (the one that is bound through the generic Keys),
 * TypeScript passes all parts of the union to CheckArguments. This leads CheckArguments to return all possible values
 */

declare function get<Obj extends object, Keys extends Arguments<Obj>>(o: Obj, ...keys: Keys): CheckArguments<Obj, Keys>

/** 
 * Tests 💥
 * */
const foo = get(obj, 'a') // Should be "2" 😢 
const foo1 = get(obj, 'b', 'c') // Should be  3 😢 
const foo2 = get(obj, 'b', 'd', 'e') // Should be "string" 😢 
const foo3 = get(obj, 'b', 'd', 'f', 'g', 'h') // Should be 4 😢 

Expected behavior: Keys gets bound to the value type passed as an argument to the function. This value type is then used for CheckArguments

Actual behavior: Keys is the entire union type Arguments<Obj>, not the subset. This leads to CheckArguments returning a too broad return type (and taking very long to evaluate ;-))

Playground Link: Click here

Related Issues: Did not find any.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu bằng cách tái hiện trường hợp suy luận generic trong TypeScript Playground được liên kết bằng phiên bản được báo cáo 4.2.0-dev.20201103, rồi so sánh các kiểu được suy luận của foo đến foo3 với kết quả mong đợi. Hoàn thành có nghĩa là mỗi lời gọi liên kết Keys với tuple được cung cấp thay vì union Arguments đầy đủ, đồng thời tránh việc đánh giá quá mức được mô tả.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
typescript
Lĩnh vực
compilers
Loại issue
Lỗi
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
25/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.