Skip to content

Inconsistent type inference when changing the order of type declarations (5.0 regression) #55666

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
SimpleCreations opened this issue Sep 7, 2023 · 4 comments
Assignees
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@SimpleCreations
Copy link

SimpleCreations commented Sep 7, 2023

πŸ”Ž Search Terms

N/A β€” not sure which part of the playground code causes the issue

πŸ•— Version & Regression Information

  • No bug on 4.9.5
  • The bug can be observed starting from 5.0.4

⏯ Playground Link

https://www.typescriptlang.org/play?#code/C4TwDgpgBA4gTgQzACwIoBkByB7AdgZQFcAjfCAGwgGNgFjKoBeAKCigB8oBnYOAS1wBzVhygBBOIhAAeHvyGjchcuVGFcAEwgAzARA0A+EZyUrjUdVt259AbmahIsRCgxFSFarXrQWbTvBIaOgA8sQAVl7mElLSga6hEV6Kyqqcljp6hvaO0PHBAGoI5IS+zkEYOAQkZJQ0dAwBLsHutV4NEPYA9F1QEFzkAsAAtBp8XB3DNgAeI4M2UAACuVxU-GAj-fPAXVR4XOPAELgjAlrT+sPYSTTDPCCUADTL4P1rfBvDW0O7+4fHI1yo0yuD4wD4+wcr3KCTCkRoTCgAG8oABtADWEBAAC5uLwBIIALq4-IYIolaAAX3szD2uB4UAAKgAlMSYfAAMRCzIAsgBRZmI-AgAC2xGw5AAFAByFlsznc-nM6UAShpQwgcG0CCo0FannqPg5fAoGi4bRoENwIQ2Vq4yJEqLl7K5vIFhIA-LjGYh6dpsHARZrmTpNcdddJrvDgI8YcE4V4DPZKcwoU5jabzQbwXgbTn6dJGQZEYy+rNjma424atmOiIPVB9XVvJQM+QzRb83m7SJcVVMKkOtJ1OjcNgAO64JOprRUcgIODQOkM50Kt3MgD6zL5HIFfMwAGE+RvGQBNAAKfNx6j4AEdStxReLyPZZ-PF1Bl8AmayXYqBVuO57oex6rq6SrXqC97QFwT4SvYGpajq0A+ggfoBkGcAhtoYa4BGIiMqh6GBpqZZHJo9pRl4jwEYy0IQOWFFVok0Y0cWSKOmB-6btuu7biBJ4XnyxJMnRkD2GwTq-muSqAXx+5Hie0nge63pEVw-okXAyaprkUAAKq4OOLhNu0PiFmRFb2qS6CmYalDFowTKWUxMQIDIIgCDhcBQAUzBGGwDb9oO5kFAFUDeuqJyatqupQAmNAAOpgsg6maZh3Z4NItG+hpGGkQx5GVlRNA0WwhH0YxlY2QlMb+Q6klceuIlpflWGhoueEQIWrVabGFWQNOKZptAbYaOeCDAMgtUWYVVnMbVjkNWiADSUACFAmIgNg2hMp63qoithIuZWXmkX5bCBb5J3Wc01YeM2daXZdDZjRNU0zYZxlIHZLbdWF4XPX2eADioQ4jmOk6AxFUA2AAbpqyZQAAZFAY1Zs2VqZQWRY0npY3uO9yDkqU0gFP1hOTcgS0FDdzFVL9T1QA2F0w7Tc1MTZjM+PWTKU1NdNvVTn1GSZNaPaFBjQyzdM1TcwAiM9r0mu2RMk91X1iw9ZmUGTBgUyQRPQ2w3r88gdO1clU29RltpZYrz2XWdPmEbl6WamVjvPZrP3izr-3G5dgcNq7aF5VpDsmxYuCjhOuAO7i8Oar2sMQAj2m6dCQtTer2XlbLd0sdRBFE4LKvjcL8uFvr9VORxklrRtW07UyRMiWtHOVs3u2Mg7yummbueModhL9UTI-Q4naeI8wlIYliLeMm3UVHEhcUctg2CrzbpH11AuimpBseTjpI1QHyJxwCAiJ77BXDA7goPzuZENx0NePQqgpRXzfIj+lvmoDwSkxngL0aNN6ryASoLwVpkwfycHycscBcDFHQAIdEiIL68GvqjPe8xMQaA5HAbAIowEbwAXAKBIDcBwMzumCBpEnJYJ-rgv+DC4BoJjlweA2BCBgCoTA0BuJyGr04eibhxC+ECMtHgWhZ8RGAOAYI3Av82BgggCKe+4CKGokJHI-G7CxESN4WATBl8cHLUEJIsAYiwGINXig8gYjRDBTBi-GOkMpz6KzoY9BxipFKJkSouunkjgimEb4rhPC+HeKcKXEJbB-6QMCfmXEe81FhK0eky6Ul5QqWZC1N2bVsK4QjAouAO84CxnKdOZ61IRD1PqWfAAkmExE2dibFFJl-TUIBYxGxpG+BcS59jfmmLiVpGj7BfigDibR28ilaTUJoEE+hETTHsEAA

πŸ’» Code

Apologies if the example is too long. I did my best to reduce it as much as possible, having encountered this bug in production.

type GraphQLNonSubSelectable =
  | string
  | Array<string | null | undefined>
  | null
  | undefined;
type GraphQLSubSelectable =
  | GraphQLObject
  | Array<GraphQLObject | null | undefined>;
type GraphQLValue = GraphQLNonSubSelectable | GraphQLSubSelectable;
type GraphQLObject = { [key: string]: GraphQLValue };

const TRANSFORMER = Symbol('TRANSFORMER');

interface SubSelectableFieldsSelectionOptions {
  [TRANSFORMER]?: TransformerReference<object, GraphQLObject>;
}

type FieldsSelectionOptions<T> = T extends GraphQLSubSelectable
  ? SubSelectableFieldsSelectionOptions
  : NonNullable<unknown>;

declare const TRANSFORMER_REFERENCE_TYPE: unique symbol;
declare const TRANSFORMER_REFERENCE_TRANSFORMER: unique symbol;
interface TransformerReference<
  TTransformer extends object,
  TType extends GraphQLObject,
> {
  [TRANSFORMER_REFERENCE_TYPE]: TType;
  [TRANSFORMER_REFERENCE_TRANSFORMER]: TTransformer;
}

type UnwrapSubSelectable<T extends GraphQLSubSelectable> = T extends Array<
  infer V
>
  ? NonNullable<V>
  : T;

interface ObjectWithTransformerOption<
  TTransformer extends object,
  TType extends GraphQLObject,
> {
  [TRANSFORMER]: TransformerReference<TTransformer, TType>;
}

type FieldPathObject<T extends GraphQLObject> = {
  [K in keyof T]?: T[K] extends infer V
    ? V extends GraphQLSubSelectable
      ? FieldPathObject<UnwrapSubSelectable<V>>
      : NonNullable<unknown>
    : never;
} & FieldsSelectionOptions<T>;

type FieldSubPathValue<V, TSubPath> = V extends GraphQLNonSubSelectable
  ? V
  : V extends GraphQLSubSelectable
  ? TSubPath extends FieldPathObject<UnwrapSubSelectable<V>>
    ? V extends GraphQLObject
      ? FieldPathValue<UnwrapSubSelectable<V>, TSubPath>
      : TSubPath extends ObjectWithTransformerOption<
          infer TTransformer,
          UnwrapSubSelectable<V>
        >
      ? TTransformer
      : unknown
    : never
  : never;

type FieldPathValue<
  T extends GraphQLObject,
  TPath extends FieldPathObject<T>,
> = {
  [K in keyof TPath]: K extends keyof T
    ? FieldSubPathValue<T[K], TPath[K]>
    : never;
}[keyof TPath];

interface FooterTransformer {
  field: unknown;
}

type Entry = {
  sys: NonNullable<unknown>;
};

type Query = {
  footerCollection?: FooterCollection;
};

type ExternalLink = Entry & {
  linkedFrom?: FooterCollection;
};

type Footer = Entry & {
  footerLinksGroupCollection?: FooterLinksGroupCollection;
};

type FooterCollection = {
  items: Footer[];
};

type FooterLinksGroup = Entry & {
  groupLink?: ExternalLink | NonNullable<unknown>;
};

type FooterLinksGroupCollection = {
  item: FooterLinksGroup;
};

type Path = {
  footerCollection: {
    items: {
      [TRANSFORMER]: TransformerReference<FooterTransformer, Footer>;
    };
  };
};

type Item = FieldPathValue<Query, Path>;

declare const x: Item;
const y: FooterTransformer | undefined = x;

πŸ™ Actual behavior

  1. Observe that the example gives the following error:
TS2322: Type 'Footer[] | undefined' is not assignable to type 'FooterTransformer | undefined'.
  Property 'field' is missing in type 'Footer[]' but required in type 'FooterTransformer'.

121 const y: FooterTransformer | undefined = x;
          ~
  1. Put type Footer between type Query and type ExternalLink. Updated example

  2. Watch the error disappear.

πŸ™‚ Expected behavior

Versions 4.9.5 an below correctly infer the types (produce no error). Changes to the order of type declarations do not affect the result.

Additional information about the issue

No response

@typescript-bot
Copy link
Collaborator

The change between v4.9.5 and v5.0.4 occurred at 3099385.

@andrewbranch
Copy link
Member

Caused by #52392

@andrewbranch andrewbranch added the Needs Investigation This issue needs a team member to investigate its status. label Sep 11, 2023
@ahejlsberg
Copy link
Member

The issue here is similar to that in #56291. The example requires resolving nested FieldPathValue instantiations more than three levels deep, and which point the type checker stops evaluating to avoid what appears to be infinite recursion. As I mention in #56291, we simply don't have a good way to determine that the instantiations eventually terminate. The reason the example works when types are reordered is that type IDs end up ordering differently and don't exhibit the same increasing ID pattern that causes termination.

We're continuing to think about better ways to handle these scenarios.

@ahejlsberg ahejlsberg added Design Limitation Constraints of the existing architecture prevent this from being fixed and removed Needs Investigation This issue needs a team member to investigate its status. labels May 29, 2024
@typescript-bot
Copy link
Collaborator

This issue has been marked as "Design Limitation" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

4 participants