Skip to content

Each tuple member type becomes union of all tuple member types when intersection with object #53091

Closed

Description

@btakita

Bug Report

I'm attempting to define a Row type which has a generic being an array of pair tuples, like a Map's entries 2d array, that is a tuple of the mapped value members (index 1), of each pair intersected with an object of the key: value members.

export type Row<Defs extends [string, any][]> = RowTuple<Defs>&TupleToProps<Defs, 0, 1>
export type RowTuple<Defs extends [string, any][]> = { [K in keyof Defs]:Defs[K][1] }
type TupleToProps<T extends readonly any[], KeyName extends keyof T[number], ValName extends keyof T[number]> = {
	[P in Exclude<keyof T, keyof readonly any[]> as T[P][KeyName]&string]:T[P][ValName]
}

This results in the following inferences:

// Row
const foo = foo_()
const foo_0 = foo[0] // number|string
const foo_1 foo[1] // number|string
const foo_col0 = foo.col0 // number
const foo_col1 = foo.col1 // string
function foo_():Row<[['col0', number], ['col1', string]]> {
	return Object.assign([123, 'bar'], {
		col0: 123,
		col1: 'bar'
	}) as Row<[['col0', number], ['col1', string]]>
}

Without the intersected object type, the more precise inferences are made:

/ RowTuple
const foo_tuple_0 = foo_tuple[0] // number
const foo_tuple_1 = foo_tuple[1] // string
function foo_tuple_():RowTuple<[['col0', number], ['col1', string]]> {
	return [123, 'bar']
}

It seems like the tuple position type inferences are not preserved.

### Tasks
- [ ] Add a draft title or issue reference here

🔎 Search Terms

typescript tuple intersection object

🕗 Version & Regression Information

5.1.0-dev.20230303

⏯ Playground Link

Playground link with relevant code

💻 Code

// We can quickly address your report if:
//  - The code sample is short. Nearly all TypeScript bugs can be demonstrated in 20-30 lines of code!
//  - It doesn't use external libraries. These are often issues with the type definitions rather than TypeScript bugs.
//  - The incorrectness of the behavior is readily apparent from reading the sample.
// Reports are slower to investigate if:
//  - We have to pare too much extraneous code.
//  - We have to clone a large repo and validate that the problem isn't elsewhere.
//  - The sample is confusing or doesn't clearly demonstrate what's wrong.

🙁 Actual behavior

foo_0 is a number|string & foo_1 is a number|string.

🙂 Expected behavior

foo_0 should be a number & foo_1 should be a string.

I believe that the tuple position type behavior should be preserved when intersected with another type.

Activity

  1. Seally commented on Mar 4, 2023

    @Seally

    Your playground link uses the type TupleToObject, which doesn't exist, and there's an extra ) in the type parameters of Row and RowTuple.

    I changed TupleToObject to TupleToProps (I assume that's the intention), and removed the extra ) and I think it works now?

  2. btakita commented on Mar 4, 2023

    @btakita
    Author

    Sorry about that. I updated the Playground Link in the OP description as well.

  3. changed the title [-]Tuple value types become union of all tuple value types when intersected with an object[/-] [+]Each tuple member type becomes union of all tuple member types when intersected with an object[/+] on Mar 5, 2023
  4. changed the title [-]Each tuple member type becomes union of all tuple member types when intersected with an object[/-] [+]Each tuple member type becomes union of all tuple member types when intersection with object[/+] on Mar 5, 2023
  5. RyanCavanaugh commented on Mar 6, 2023

    @RyanCavanaugh
    Member

    image

    Using the example from that link but adding export to show the types:

    export type Row<Defs extends [string, any][]> = RowTuple<Defs>&TupleToProps<Defs, 0, 1>
    export type RowTuple<Defs extends [string, any][]> = { [K in keyof Defs]:Defs[K][1] }
    type TupleToProps<T extends readonly any[], KeyName extends keyof T[number], ValName extends keyof T[number]> = {
    	[P in Exclude<keyof T, keyof readonly any[]> as T[P][KeyName]&string]:T[P][ValName]
    }
    // Row
    const foo = foo_()
    export const foo_0 = foo[0] // number|string
    export const foo_1 = foo[1] // number|string
    export const foo_col_0 = foo.col_0 // number
    export const foo_col_1 = foo.col_1 // string
    function foo_():Row<[['col_0', number], ['col_1', string]]> {
    	return Object.assign([123, 'bar'], {
    		col_0: 123,
    		col_1: 'bar'
    	}) as Row<[['col_0', number], ['col_1', string]]>
    }
    // RowTuple
    const foo_tuple = foo_tuple_()
    export const foo_tuple_0 = foo_tuple[0] // number
    export const foo_tuple_1 = foo_tuple[1] // string
    function foo_tuple_():RowTuple<[['col_0', number], ['col_1', string]]> {
    	return [123, 'bar']
    }

    the .d.ts shows all as-expected

    export declare const foo_0: number;
    export declare const foo_1: string;
    export declare const foo_col_0: number;
    export declare const foo_col_1: string;
    export declare const foo_tuple_0: number;
    export declare const foo_tuple_1: string;

    Can you clarify?

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

    Needs More InfoThe issue still hasn't been fully clarified

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions