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

['a' | 'b'] expect to receive ['a'] #55225

Description

@crazyair

Bug Report

DeepNamePath<T> result is equal to ['a'] | ['b'], but result different

🔎 Search Terms

🕗 Version & Regression Information

  • This is a crash
  • This changed between versions ______ and _______
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about _________
  • I was unable to test this on prior versions because _______

⏯ Playground Link

Playground link with relevant code

💻 Code

export type DeepNamePath<T = any> = T extends Record<string, any>
  ? {
    [P in keyof T]: [P] | DeepNamePath<T[P]>;
  }[keyof T]
  : never;

type ddd<T = any> = ['a'] | ['b']  // result is ['a']
// type ddd<T = any> = DeepNamePath<T>;  // result is ['a'] | ['b']


function func<T = any, T1 extends ddd<T> = ddd<T>>(data: T, params: T1) {
  console.log('data', data);
  return params;
}

export const d = func({ a: '', b: '' }, ['a']);
type result = typeof d
//  ^?

🙁 Actual behavior

['a']

🙂 Expected behavior

['a' , 'b']

Activity

  1. MartinJohns commented on Aug 1, 2023

    @MartinJohns
    Contributor

    🙂 Expected behavior

    ['a' , 'b']

    Why do you expect this behaviour? What's your reasoning and thought process?

    The return argument of your function is just params, and params is just typed T1. This type argument is inferred from the provided parameter, and you provide ["a"] as the value. This looks just fine.

  2. crazyair commented on Aug 1, 2023

    @crazyair
    Author

    🙂 Expected behavior

    ['a' , 'b']

    Why do you expect this behaviour? What's your reasoning and thought process?

    The return argument of your function is just params, and params is just typed T1. This type argument is inferred from the provided parameter, and you provide ["a"] as the value. This looks just fine.

    I want result is ['a'], will be used to obtain the value of data

  3. crazyair commented on Aug 1, 2023

    @crazyair
    Author

    func({ a: { a1: string }, b: number }, ['a']);

    input ['a', 'a1'] result string
    input b result number

  4. crazyair commented on Aug 1, 2023

    @crazyair
    Author
     export type DeepNamePath<T = any> = T extends Record<string, any>
       ? {
    -      [P in keyof T]: [P] | DeepNamePath<T[P]>;
    +      [P in keyof T]: [P] | [...DeepNamePath<T[P]>];
         }[keyof T]
       : never;
    

    If I write this way, I can get ['a'], but it requires DeepNamePath to return an array

  5. Andarist commented on Aug 1, 2023

    @Andarist
    Contributor

    Your second argument gets widened to string[] in the first inference pass. That's gathered as the inference candidate, it fails the constraint check so the constraint gets selected as the inference result.

    TS doesn't recognize that your T1 has a tuple-like constraint (getBaseConstraintOfType just returns unknown). I suspect that's because it's a recursive type without a direct hint that it returns a tuple type. When u wrapped the second union member with [...T] you essentially gave it that hint and that's why it works.

    For similar reasons this one work:

    export type DeepNamePath<T = any> = T extends Record<string, any>
      ? {
        [P in keyof T]: readonly [P] | DeepNamePath<T[P]>;
      }[keyof T]
      : never;
    
    type ddd<T = any> = DeepNamePath<T>;
    
    function func<T = any, const T1 extends ddd<T> = ddd<T>>(data: T, params: T1) {
      console.log('data', data);
      return params;
    }
    
    export const d = func({ a: '', b: '' }, ['a']);

    This one doesn't really recognize the tuple-like base constraint but it introduces a const context and has a similar effect on the overall thing.

  6. crazyair commented on Aug 1, 2023

    @crazyair
    Author

    Your second argument gets widened to string[] in the first inference pass. That's gathered as the inference candidate, it fails the constraint check so the constraint gets selected as the inference result.

    TS doesn't recognize that your T1 has a tuple-like constraint (getBaseConstraintOfType just returns unknown). I suspect that's because it's a recursive type without a direct hint that it returns a tuple type. When u wrapped the second union member with [...T] you essentially gave it that hint and that's why it works.

    For similar reasons this one work:

    export type DeepNamePath<T = any> = T extends Record<string, any>
      ? {
        [P in keyof T]: readonly [P] | DeepNamePath<T[P]>;
      }[keyof T]
      : never;
    
    type ddd<T = any> = DeepNamePath<T>;
    
    function func<T = any, const T1 extends ddd<T> = ddd<T>>(data: T, params: T1) {
      console.log('data', data);
      return params;
    }
    
    export const d = func({ a: '', b: '' }, ['a']);

    This one doesn't really recognize the tuple-like base constraint but it introduces a const context and has a similar effect on the overall thing.

    I thought of const, but I couldn't write it at the time. const T1 extends ddd<T> = ddd<T>

  7. jcalz commented on Aug 1, 2023

    @jcalz
    Contributor

    Since this hasn't been said explicitly yet: this isn't a bug in TypeScript so this isn't really the right place for this discussion. Stack Overflow or the TS Discord would be more appropriate venues. 叶枫 (@crazyair), could you please close the issue to free up the TS team's time? Once it's closed I imagine anyone who's still interested could continue discussing here.

    edit: Thanks! 🙏

  8. crazyair commented on Aug 2, 2023

    @crazyair
    Author

    Mateusz Burzyński (@Andarist)

    export type DeepNamePath<T = any> = T extends Record<string, any>
      ? {
          [P in keyof T]: readonly [P] | DeepNamePath<T[P]>;
        }[keyof T]
      : never;
    
    type ddd<T = any> = DeepNamePath<T>;
    
    function func<T = any, const T1 extends ddd<T> = ddd<T>>(data: T, params: T1) {
      console.log('data', data);
      return params;
    }
    
    // export const d = func({ a: '', b: '' }, ['a']);
    
    export interface ColumnType<
      RecordType = any,
      T1 extends DeepNamePath<RecordType> = DeepNamePath<RecordType>,
    > {
      dataIndex?: T1;
      render?: (value: T1) => any;
    }
    
    export interface DemoProps<T = any> {
      data: readonly T[];
      columns: ColumnType<T, DeepNamePath<T>>[];
    }
    
    export const result: DemoProps<{ a: number; b: string }> = {
      data: [{ a: 1, b: '' }],
      columns: [{ dataIndex: ['a'], render: value => value }],
    };

    value type is readonly ["a"] | readonly ["b"], i want readonly ["a"]

  9. crazyair commented on Aug 2, 2023

    @crazyair
    Author
    -export interface ColumnType<RecordType = any, T1 extends DeepNamePath<RecordType> = any> {
    +export interface ColumnType<RecordType = any, const T1 extends DeepNamePath<RecordType> = any> {
       dataIndex?: T1;
       render?: (value: T1) => any;
     }

    'const' modifier can only appear on a type parameter of a function, method or classts(1277)

  10. Andarist commented on Aug 2, 2023

    @Andarist
    Contributor

    To make it work you need to be in some inference context and that means that you need a function call to make it work (and annotate its type params as const). This kinda should work:

    export type DeepNamePath<T = any> = T extends Record<string, any>
      ? {
          [P in keyof T]: readonly [P] | readonly [...DeepNamePath<T[P]>];
        }[keyof T]
      : never;
    
    declare function make<T, const T2 extends readonly DeepNamePath<T>[]>(arg: {
      data: T[];
      columns: {
        [K in keyof T2]: {
          dataIndex: T2[K];
          render: (value: T2[K]) => void;
        };
      };
    }): void;
    
    export const result = make({
      data: [{ a: 1, b: "" }],
      columns: [{ dataIndex: ["a"], render: (value) => value }],
    });

    but it doesn't because TS doesn't currently handle context-sensitive functions in reverse-mapped types that well. It's something I want to improve in #54029

  11. crazyair commented on Aug 2, 2023

    @crazyair
    Author

    I need

    const demo: DemoProps<xx> = xxx
  12. crazyair commented on Aug 2, 2023

    @crazyair
    Author

    If list there are 2 pieces of data (parameter) value: "a" | "b"
    If list there are 1 pieces of data (parameter) value: "a"

    declare function func<TData = any, const T1 extends keyof TData = any>(data: {
      data: TData[];
      list: {
        key?: T1;
        render?: (value: T1) => any;
      }[];
    }): any;
    
    func({
      data: [{ a: 1, b: '2' }],
      list: [
        { key: 'a', render: value => value },
        { key: 'b', render: value => value },
      ],
    });
  13. locked as resolved and limited conversation to collaborators on Aug 2, 2023
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions