Skip to content

TS2345 Generic parameter type, contextually inferred as a union by return at call site, will not accept all properties on object literal #43044

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
6XGate opened this issue Mar 2, 2021 · 3 comments

Comments

@6XGate
Copy link

6XGate commented Mar 2, 2021

Bug Report

πŸ”Ž Search Terms

  • TS2345
  • Object literal may only specify known properties
  • several other combination I cannot recall

πŸ•— Version & Regression Information

  • This changed between sometime between versions 3.3.3 and 3.5.1

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

type GetGroupKeywordsSelector = {
  profileId: string;
  groupId: string;
  metricsRange: [number, number];
};

type GetOutlierKeywordsSelector = {
  profileId: string;
  productId?: string;
  metricsRange: [number, number];
};

type MaybeGetGroupKeywordsSelector = GetGroupKeywordsSelector & { outliers?: false };
type MaybeGetOutlierKeywordsSelector = GetOutlierKeywordsSelector & { outliers: true };

type GetKeywordsSelector = MaybeGetGroupKeywordsSelector|MaybeGetOutlierKeywordsSelector;

function getKeywords(_selector: GetKeywordsSelector) {
  // Queries keywords
}

function ensureProfileId<T extends { profileId: string }>(selector: Omit<T, "profileId">): T {
  return { ...selector, profileId: "2" } as T;
}

// Compiles fine
getKeywords({
  productId: "23",
  profileId: "2",
  outliers: true,
  metricsRange: [Date.now()-500000, Date.now()]
});

// Failes with TS2345 on the productId property.
getKeywords(ensureProfileId({
  productId: "23",
  outliers: true,
  metricsRange: [Date.now()-500000, Date.now()]
}));

πŸ™ Actual behavior

The code raises a TS2345 from the compiler when attempting to use any property of a union that is not common to all types of the union. This only occurs when passing an object literal to a generic function which has inferred its generic type from contextual typing of the return type.

In this case; the language service does indicate that it has properly determined the correct type, but will not accept any uncommon property belong to only one type of the union.

πŸ™‚ Expected behavior

In less complex contexts; this would work, and seems as though it should work here.

@craigphicks
Copy link

T extends {profileId:string} was also a constraint on the input (as well as the output). When you Omit that constraint on the input it becomes 'no constraint', e.g., any. Replacing the input constraint with any
seems to make the problem go away.

function ensureProfileId<T extends { profileId: string }>(selector: any): T {
  return { ...selector, ...{profileId: "2" } as T;
}

@6XGate
Copy link
Author

6XGate commented Mar 3, 2021

@craigphicks, unfortunately the input must be typed to modified generic type to ensure the correct object is being passed. The language service shows it determines the correct type. This may actually be an issue with Omit since writing a non-generic form of the ensureProfileId function products similar results AND will not itself compile.

@6XGate
Copy link
Author

6XGate commented Mar 3, 2021

Alright; after determining the issue is indeed with Omit and searching on that. What I found is that Omit and Pick do not distribute over unions and that this is working as intended per #28339

Creating and using a distributed form will fix this:

type UnionKeys<T> = T extends any ? keyof T : never
type DistributivePick<T, K extends UnionKeys<T>> = T extends any ? Pick<T, Extract<keyof T, K>> : never;
type DistributiveOmit<T, K extends UnionKeys<T>> = T extends any ? Omit<T, Extract<keyof T, K>> : never;

@6XGate 6XGate closed this as completed Mar 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants