Skip to content
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

Return type inference does not infer union #32237

Closed
AnyhowStep opened this issue Jul 3, 2019 · 7 comments
Closed

Return type inference does not infer union #32237

AnyhowStep opened this issue Jul 3, 2019 · 7 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@AnyhowStep
Copy link
Contributor

AnyhowStep commented Jul 3, 2019

TypeScript Version: 3.5.1, 3.3.3

Search Terms:

return type, infer, union

Code

/**
 * + TS 3.5.1
 * + TS 3.3.3
 */
export interface CreateResult {
    contributors : (
        & {
            someType : string,
        }
        & {
            subObject : (
                | (
                    & {
                        someId : number,
                        prop0 : number,
                    }
                    & {
                        THIS_IS_REQUIRED_WHEN_SOME_ID_IS_SET : "BUT IT IS NOT SET",
                    }
                )
                | {
                    someId? : undefined,
                    prop0 : number,
                }
            ),
        }
    )[];
}

function foo (arg : { someType : string, subObject : { prop0 : number } }) {
    if (Math.random() > 0.5) {
        return arg;
    } else {
        const subObject = {
            someId : 0,
            prop0 : 0,
        };
        /*
            Expected and Actual are the same.
            {
                someType: string;
                subObject: {
                    someId: number;
                    prop0: number;
                };
            }
        */
        const result = {
            ...arg,
            subObject : {
                ...subObject,
            },
        };
        return result;
    }
}

/*
    Expected:
    | {
        someType: string;
        subObject: {
            prop0: number;
        };
    }
    | {
        someType: string;
        subObject: {
            someId: number,
            prop0: number;
        };
    }

    Actual:
    {
        someType: string;
        subObject: {
            prop0: number;
        };
    }
*/
const wtf = foo({
    someType : "",
    subObject : {
        prop0 : 0,
    }
});

/**
 * It is now possible for `wtf` to have `someId` be `number`,
 * but `THIS_IS_REQUIRED_WHEN_SOME_ID_IS_SET` is not set.
 */
const x : CreateResult["contributors"][number] = wtf;

Expected behavior:

wtf should be a union of types.

Actual behavior:

wtf is not a union of types.

Playground Link: Playground

Related Issues:

I'll keep searching but I couldn't find anything. There were a bunch with the same keywords but they seemed unrelated, focusing on literals and generics.

@AnyhowStep
Copy link
Contributor Author

Smaller repro, foo() does not need to have arguments.

Playground

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Jul 3, 2019

Interestingly enough, if you replace,

return arg;

with,

return {
    someType : "",
    subObject : {
        prop0 : 0,
    }
};

The inferred return type of foo() is correct, even though both statements return the same type.

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Jul 3, 2019
@RyanCavanaugh
Copy link
Member

This is normal subtype reduction - the type T | U when U is a subtype of T is reduced to T during most inference operations.

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Jul 3, 2019

Facepalms
Right. That makes sense. I forgot about that.

It makes for unsound code in this case but soundness was never a goal.

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Jul 3, 2019

Wait, that doesn't explain why changing the return statement to a different form makes it infer the union.

Both return arg; and return { /*properties*/ }; return the same type. One does not give the desired inferred return type. The other does.

Or is the key phrase here "most inference operations"?

And it just so happens that using a different kind of return statement changes the inference?

Pretty unintuitive, and it doesn't seem like I should rely on the behavior. I guess an explicit return type annotation is better in this case?

@AnyhowStep AnyhowStep reopened this Jul 3, 2019
@jack-williams
Copy link
Collaborator

Wait, that doesn't explain why changing the return statement to a different form makes it infer the union.

Maybe due to object literal freshness?

@AnyhowStep
Copy link
Contributor Author

You're probably right but I wonder if it was a conscious decision and why. It seems like a rather obscure rule.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants