Skip to content

Incorrect Recursive Union Emmission #31605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ericwooley opened this issue May 26, 2019 · 8 comments · Fixed by #31544
Closed

Incorrect Recursive Union Emmission #31605

ericwooley opened this issue May 26, 2019 · 8 comments · Fixed by #31544
Assignees
Labels
Bug A bug in TypeScript

Comments

@ericwooley
Copy link

TypeScript Version: 3.4.5 & typescript@3.5.0-dev.20190525

Search Terms:
Recursive Emit union
Code

export const testRecFun= <T extends Object>(parent: T) => {
    return {
      result: parent,
      deeper: <U extends Object>(child: U) =>
        testRecFun<T & U>({ ...parent, ...child })
    };
}


let p1 = testRecFun({one: '1'})
console.log(p1.result.one)
let p2 = p1.deeper({two: '2'})
console.log(p2.result.one, p2.result.two);
let p3 = p2.deeper({three: '3'})
console.log(p3.result.one, p3.result.two, p3.result.three);

Expected behavior:
each depth would produce a unique generic for U

export declare const testRecFun: <T extends Object>(parent: T) => {
    result: T;
    deeper: <U extends Object>(child: U) => {
        result: T & U;
        deeper: <Ua extends Object>(child: Ua) => {
            result: T & U & Ua;
            deeper: <Ub extends Object>(child: Ub) => {
                result: T & U & Ua & Ub;
                deeper: <Uc extends Object>(child: Uc) => {
                    result: T & U & Ua & Ub & Uc;
                    deeper: <Ud extends Object>(child: Ud) => {
                        result: T & U & Ua & Ub & Uc & Ud;
                        deeper: <Ue extends Object>(child: Ue) => {
                            result: T & U & Ua & Ub & Uc & Ud & Ue;
                            deeper: <Uf extends Object>(child: Uf) => {
                                result: T & U & Ua & Ub & Uc & Ud & Ue & Uf;
                                deeper: <Ug extends Object>(child: Ug) => {
                                    result: T & U & Ua & Ub & Uc & Ud & Ue & Uf & Ug;
                                    deeper: <Uh extends Object>(child: Uh) => {
                                        result: T & U & Ua & Ub & Uc & Ud & Ue & Uf & Ug & Uh;
                                        deeper: <Ui extends Object>(child: Ui) => {
                                            result: T & U & Ua & Ub & Uc & Ud & Ue & Uf & Ug & Uh & Ui;
                                            deeper: <Uj extends Object>(child: Uj) => any;
                                        };
                                    };
                                };
                            };
                        };
                    };
                };
            };
        };
    };
};

Actual behavior:

export declare const testRecFun: <T extends Object>(parent: T) => {
    result: T;
    deeper: <U extends Object>(child: U) => {
        result: T & U;
        deeper: <U extends Object>(child: U) => {
            result: T & U & U;
            deeper: <U extends Object>(child: U) => {
                result: T & U & U & U;
                deeper: <U extends Object>(child: U) => {
                    result: T & U & U & U & U;
                    deeper: <U extends Object>(child: U) => {
                        result: T & U & U & U & U & U;
                        deeper: <U extends Object>(child: U) => {
                            result: T & U & U & U & U & U & U;
                            deeper: <U extends Object>(child: U) => {
                                result: T & U & U & U & U & U & U & U;
                                deeper: <U extends Object>(child: U) => {
                                    result: T & U & U & U & U & U & U & U & U;
                                    deeper: <U extends Object>(child: U) => {
                                        result: T & U & U & U & U & U & U & U & U & U;
                                        deeper: <U extends Object>(child: U) => {
                                            result: T & U & U & U & U & U & U & U & U & U & U;
                                            deeper: <U extends Object>(child: U) => any;
                                        };
                                    };
                                };
                            };
                        };
                    };
                };
            };
        };
    };
};

// intermediate steps are lost.
let p1 = testRecFun({one: '1'})
console.log(p1.result.one)
let p2 = p1.deeper({two: '2'})
console.log(p2.result.one, p2.result.two);
let p3 = p2.deeper({three: '3'})
console.log(p3.result.one, p3.result.two, p3.result.three) // error p3.result.two is not ...

Playground Link:

I didn't make this in the playground, but I made a repo for reproduction.
https://github.com/ericwooley/ts-recursive-test

Related Issues:

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label May 28, 2019
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.6.0 milestone May 28, 2019
@jcalz
Copy link
Contributor

jcalz commented May 28, 2019

I'm not reproducing this bug in my local environment with either 3.4.5 or 3.5.0-dev.20190525. The IntelliSense does show U & U & U & U but those are just unrelated types with the same names. The actual concrete results (e.g., p3.result.two) look correct to me. What am I missing?

looksokaytome

@weswigham
Copy link
Member

The typechecking is correct - the declaration emit is just wrong. #31544 should fix this.

@jcalz
Copy link
Contributor

jcalz commented May 28, 2019

So in the "actual behavior" from the original post,

console.log(p3.result.one, p3.result.two, p3.result.three) // error p3.result.two is not ...

that error message is made up? I just can't trust people on the Internet! 😒

@weswigham
Copy link
Member

that error message is made up? I just can't trust people on the Internet!

That's the error you get if your consume our current declaration output - since every type variable is named U you only ever get one member after the first.

@jcalz
Copy link
Contributor

jcalz commented May 28, 2019

Right, it just occurred to me that the actual issue is something like... you emit a declaration file for the first bit and then use those declarations with the collision-named types and fun times happen. Trust in Internet people restored! I'm going to go send some money to that Nigerian prince!

@ericwooley
Copy link
Author

ericwooley commented May 28, 2019

I'm glad my trust is restored! Sorry I was a bit behind in replying, but thanks everyone for picking up the slack!

31544 does look like it would resolve the issue to me.

It seems like there is a need for some kind of pointer or reference in type emissions so that cases like recursion could be followed the by the type checker the same was as they would be followed by interpreting the original code. Does anyone know if that idea has that already been discussed anywhere I could follow along?

@weswigham
Copy link
Member

It seems like there is a need for some kind of pointer or reference in type emissions so that cases like recursion could be followed the by the type checker the same was as they would be followed by interpreting the original code.

The checker is actually what is converting the type into the node tree. It just wasn't picking good names for the nodes it manufactured, since it wasn't tracking names it'd already used~

@ericwooley
Copy link
Author

🎉 🙌

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants