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

[gql_code_builder ] bugfix for reuse-fragments feature #417

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

dehypnosis
Copy link
Contributor

@dehypnosis dehypnosis commented Sep 1, 2023

Hi @knaeckeKami
This PR is a succession of #413
After the above PR, our team have developed our ongoing project with dev release version which includes above PR.
And we made a small improvement and a bugfix to make the reuse-fragment feature stable.
So here is a PR to finalize this feature.

Changes

  • Fix inline fragments data class serialization runtime error with ferry_generator:graphql_builder.data_class_config.reuse_fragments option enabled.
  • Fix WhenExtension to not to create redundant mappers for interfaces which doesn't make sense because interfaces don't have __typename. (But for the backward compatibility, this change is only applied when reuse_ragment option is enabled.)

…e-fragments.

- Fix reuse-fragments for inline fragments serialization runtime error.
- Fix reuse-fragments for inline fragments:
  - Below query will consider  CommunityContent interface while deciding reuse-fragment of CommunityFeed which implements CommunityContent.
    ```graphql
    node: {
        ... on CommunityContent {
            liked
            viewed
        }
        ... on CommunityFeed {
            ...CommunityFeedFragment
        }
        ... on CommunityReply {
            ...CommunityReplyFragment
        }
    }
    ```
  - Additionally fix WhenExtension to not to create redundant mappers for interfaces which doesn't make sense because interfaces don't have __typename.

(cherry picked from commit 7206742)
@knaeckeKami
Copy link
Collaborator

Thanks, I'll have a proper look later.

One thing that i noticed on the first glance, you seem to have implemented another way of calculating the possibleTypes map.

Would it be feasible to leverage https://github.com/gql-dart/gql/blob/master/codegen/gql_code_builder/lib/src/utils/possible_types.dart instead ?

@knaeckeKami
Copy link
Collaborator

sorry for the late response!
please pull the latest changes from master to ci succeeds

@dehypnosis
Copy link
Contributor Author

@knaeckeKami how are you doing well? just merged upstream/master branch.

@knaeckeKami
Copy link
Collaborator

Thanks! It seems there are now difference in the generated code in end_to_end_test, despite the reuse_fragments flag being disabled.
I'll look into why this is.

@dehypnosis
Copy link
Contributor Author

@knaeckeKami oh, sorry for unexpected trouble. Is that related to WhenExtension ?

@knaeckeKami
Copy link
Collaborator

Not sure yet, didn't have a proper look, but you can check the diff in the CI output: https://github.com/gql-dart/gql/actions/runs/6485343593/job/17611211975?pr=417

Comment on lines 119 to 124
superclassSelections: {
name: SourceSelections(url: null, selections: selections),
...Map.fromEntries(superclassSelections.entries.map((e) => MapEntry(
"${e.key}__as${inlineFragment.typeCondition!.on.name.value}",
e.value))),
},
Copy link
Contributor Author

@dehypnosis dehypnosis Oct 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@knaeckeKami I think the cause is that this fix about buildInlineFragmentClasses is not checking the feature flag.
This fix is made to let ..FragmentData classes cover more interfaces like by adding GheroFieldsFragment__asHuman interface to GheroFieldsFragmentData__asHuman. And I think changes made by this are all additions for missing interfaces but no regressions of existing interfaces. So there would be no regression issue even without the feature flag.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just run the code generation without the flag and unfortunately, it produces code that does not compile.

the following graphql document caues problems:

query HeroWithInterfaceSubTypedFragments($episode: Episode!) {
  hero(episode: $episode) {
    ...heroFieldsFragment
  }
}

fragment heroFieldsFragment on Character {
  id
  name

  ...on Human {
    ...humanFieldsFragment
  }

  ...on Droid {
    ...droidFieldsFragment
  }
}

fragment humanFieldsFragment on Human {
  homePlanet
  friends {
    ...on Droid {
      id
      name
      ...droidFieldsFragment
    }
    ...on Human {
      id
      name
      homePlanet
    }
  }
}

fragment droidFieldsFragment on Droid {
  primaryFunction
}

this adds another superclass to GheroFieldsFragmentData__asHuman which conflicts with the implementation:

'GheroFieldsFragmentData__asHuman.friends' ('BuiltList<GheroFieldsFragmentData__asHuman_friends?>? Function()') isn't a valid override of 'GheroFieldsFragment__asHuman.friends' ('BuiltList<GheroFieldsFragment__asHuman_friends?>? Function()'). (Documentation) 
The member being overridden (hero_with_interface_subtyped_fragments.data.gql.dart:151).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@knaeckeKami sorry to bother you, I will turn off new logics when there is no feature flag. Will share again soon.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no worries! i really appreciate you working on this! :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@knaeckeKami
How are you doing well? Finally came back here.
Sorry for late response. Just made the update. 😀

@knaeckeKami
Copy link
Collaborator

Thanks!

A user reported some issues with unions with a lot of possible values, see here:

https://github.com/LiLatee/flutter_ferry_unions_issue

I noticed that the DFS searches in all permutations of the given union types, which leads to exponential code generation time .

This is a possible fix:

https://github.com/gql-dart/gql/tree/possible_reuse_fragment_fix

Please add a test that checks if this bug is fixed by this PR

@LiLatee
Copy link
Contributor

LiLatee commented Dec 21, 2023

Hi @knaeckeKami @dehypnosis! 😄

I've prepared a repo with an example that generates a code with errors. In short, overridden fields have a wrong return type. I think that the problem exists because I have two different fragments defined on a single type defined in schema. In my case I have two fragments::
ActivityB and ActivityBWithoutSharedItem defined on type ActivityB.

Is there a quick solution for that? 🤔

@LiLatee
Copy link
Contributor

LiLatee commented Jan 1, 2024

I've spent a few more hours and created an even much simpler example to reproduce the issue and I really cannot understand what's the problem here.
image

I've found three solutions to fix the issue by modifying fragments:

  1. Remove import. Just import (even without using it) causes the error.
image
  1. Remove one fragment and use its content directly. Removing ItemEdge here and pasting that fragment's content fixes the issue
image image
  1. Or removing one union field activityData
image

Each of these modifications fixes the issue and I need to use just one of them.

Is there a chance that you could take a look at that in the near future? 🙏

@knaeckeKami
Copy link
Collaborator

Hi!

I also looked a bit into it.
I think the issue even occurs in the end_to_end_test in this query:

hero_with_interface_subtyped_fragments.graphql.

query HeroWithInterfaceSubTypedFragments($episode: Episode!) {
  hero(episode: $episode) {
    ...heroFieldsFragment
  }
}

fragment heroFieldsFragment on Character {
  id
  name

  ...on Human {
    ...humanFieldsFragment
  }

  ...on Droid {
    ...droidFieldsFragment
  }
}

fragment humanFieldsFragment on Human {
  homePlanet
  friends {
    ...on Droid {
      id
      name
      ...droidFieldsFragment
    }
    ...on Human {
      id
      name
      homePlanet
    }
  }
}

fragment droidFieldsFragment on Droid {
  primaryFunction
}

And it seems to be triggered by Inline fragment spreads with typeconditon where the selection is just one fragment spread.

But I don't know much more yet.

@knaeckeKami
Copy link
Collaborator

knaeckeKami commented Jan 5, 2024

I now understand what the issue is.

I found a way to fix it in gql_code_builder, but not sure if this breaks smth else.

@LiLatee can you try

dependency_overrides:
 gql_code_builder:
    git:
        url: https://github.com/gql-dart/gql/tree/repro_reuse_fragments
        ref: repro_reuse_fragments
        path: codegen/gql_code_builder

and give me feedback?

@LiLatee
Copy link
Contributor

LiLatee commented Jan 6, 2024

@knaeckeKami
Unfortunately still the same issue on the example repository 😞
image

btw I guess that's a proper dependency overrides

  gql_code_builder:
    git:
      ref: repro_reuse_fragments
      url: https://github.com/gql-dart/gql.git
      path: codegen/gql_code_builder

@knaeckeKami
Copy link
Collaborator

yes, thanks for fixing the broken link.

Sorry. I did not push all the code. please try again.

@LiLatee
Copy link
Contributor

LiLatee commented Jan 6, 2024

I am getting other errors right now
image

I can add Data to the name, but not sure if it's fine.
image

I don't have time right now, but I can check it more deeply tomorrow

@knaeckeKami
Copy link
Collaborator

knaeckeKami commented Jan 6, 2024

Ok, I had a suspicion that it would be not that easy ;)

Would appreciate if you could create failing reproduction to the end_to_end_test repo.

But I know now what the issue is, and I think I can find a fix in the near future.

@LiLatee
Copy link
Contributor

LiLatee commented Jan 6, 2024

Would appreciate if you could create failing reproduction to the end_to_end_test repo.

Sure, I can try to take care of it tomorrow 👌

But I know now what the issue is, it's only and I think I can find a fix in the near future.

Would be awesome 🤞

Copy link
Collaborator

@knaeckeKami knaeckeKami left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented on the code I think is the culprit

typeOverrides: typeOverrides,
superclassSelections: {
name: SourceSelections(url: null, selections: selections),
if (dataClassAliasMap.isNotEmpty)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this condition is not correct.
Inline Fragments can add new fields in selections which can cause conflicts in extending the fragments.

It also causes the build error in the end to end test package.

When Removing this, it works again

@LiLatee
Copy link
Contributor

LiLatee commented Jan 7, 2024

Would appreciate if you could create failing reproduction to the end_to_end_test repo.

@knaeckeKami should I create a failing case for the code before you commented that condition above?
Because your comment removes some part of fragment reusing. E.g. before that change my user.data.gql.dart file had 212 lines and now it has 712, because new fragments (that already exist) are created again.

@knaeckeKami
Copy link
Collaborator

Failing in the sense that it's more code or that the code doesn't compile?

Anyway, yes please share.

I know that this might increase the code size but this optimization is not correct in the way it is now. But maybe we can find I way to make it work again.

@dehypnosis
Copy link
Contributor Author

Sorry guys, I'm busy doing my day works nowadays. I will dive into to take a look in this weekend if the issue is not resolved until then.

@LiLatee
Copy link
Contributor

LiLatee commented Jan 8, 2024

@knaeckeKami oh I've finally found one issue. It looks like the order of the imports in .graphql files is important.
e.g.
Case 1:
image
image

Case 2:
image
image

This example uses repro_reuse_fragments version (commit 6ae9dfa)

@knaeckeKami
Copy link
Collaborator

thanks!

@LiLatee implemented another approach to reuse fragment classes in #437 . this is just an experiment at this point, but feel free to try it out.

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

Successfully merging this pull request may close these issues.

3 participants