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

[Merged by Bors] - bevy_reflect_derive: Tidying up the code #4712

Closed
wants to merge 20 commits into from

Conversation

MrGVSV
Copy link
Member

@MrGVSV MrGVSV commented May 10, 2022

Objective

The bevy_reflect_derive crate is not the cleanest or easiest to follow/maintain. The lib.rs file is especially difficult with over 1000 lines of code written in a confusing order. This is just a result of growth within the crate and it would be nice to clean it up for future work.

Solution

Split bevy_reflect_derive into many more submodules. The submodules include:

  • container_attributes - Code relating to container attributes
  • derive_data - Code relating to reflection-based derive metadata
  • field_attributes - Code relating to field attributes
  • impls - Code containing actual reflection implementations
  • reflect_value - Code relating to reflection-based value metadata
  • registration - Code relating to type registration
  • utility - General-purpose utility functions

This leaves the lib.rs file to contain only the public macros, making it much easier to digest (and fewer than 200 lines).

By breaking up the code into smaller modules, we make it easier for future contributors to find the code they're looking for or identify which module best fits their own additions.

Metadata Structs

This cleanup also adds two big metadata structs: ReflectFieldAttr and ReflectDeriveData. The former is used to store all attributes for a struct field (if any). The latter is used to store all metadata for struct-based derive inputs.

Both significantly reduce code duplication and make editing these macros much simpler. The tradeoff is that we may collect more metadata than needed. However, this is usually a small thing (such as checking for attributes when they're not really needed or creating a ReflectFieldAttr for every field regardless of whether they actually have an attribute).

We could try to remove these tradeoffs and squeeze some more performance out, but doing so might come at the cost of developer experience. Personally, I think it's much nicer to create a ReflectFieldAttr for every field since it means I don't have to do two Option checks. Others may disagree, though, and so we can discuss changing this either in this PR or in a future one.

Out of Scope

Some documentation has been added or improved, but ultimately good docs are probably best saved for a dedicated PR.

🔍 Focus Points (for reviewers)

I know it's a lot to sift through, so here is a list of key points for reviewers:

  • The following files contain code that was mostly just relocated:
    • reflect_value.rs
    • registration.rs
  • container_attributes.rs was also mostly moved but features some general cleanup (reducing nesting, removing hardcoded strings, etc.) and lots of doc comments
  • Most impl logic was moved from lib.rs to impls.rs, but they have been significantly modified to use the new ReflectDeriveData metadata struct in order to reduce duplication.
  • derive_data.rs and field_attributes.rs contain almost entirely new code and should probably be given the most attention.
  • Likewise, from_reflect.rs saw major changes using ReflectDeriveData so it should also be given focus.
  • There was no change to the lib.rs exports so the end-user API should be the same.

Prior Work

This task was initially tackled by @NathanSWard in #2377 (which was closed in favor of this PR), so hats off to them for beating me to the punch by nearly a year!


Changelog

  • [INTERNAL] Split bevy_reflect_derive into smaller submodules
  • [INTERNAL] Add ReflectFieldAttr
  • [INTERNAL] Add ReflectDeriveData
  • Add BevyManifest::get_path_direct() method (bevy_macro_utils)

Copy link
Contributor

@nicopap nicopap left a comment

Choose a reason for hiding this comment

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

Very good. A bit disappointed the line count didn't drastically drop with the refactor.

A few minor comments, mostly to do with formatting. Please say if you think they don't improve the code.

@IceSentry IceSentry added A-Reflection Runtime information about types C-Code-Quality A section of code that is hard to understand or change labels May 10, 2022
MrGVSV and others added 3 commits May 10, 2022 08:51
@MrGVSV
Copy link
Member Author

MrGVSV commented May 10, 2022

I'll probably move some of the constants into their own module as well. This would include making things like "PartialEq" a constant rather than a regular string.

@alice-i-cecile
Copy link
Member

Generally much cleaner. More docs will help; it's still pretty rough to follow as a first-time reader.

@MrGVSV
Copy link
Member Author

MrGVSV commented May 11, 2022

More docs will help; it's still pretty rough to follow as a first-time reader.

Yeah I left out a lot of docs so that they could be more of a focus in a separate PR. I'll try to fill in what I can, though, at least for internal items.

@MrGVSV
Copy link
Member Author

MrGVSV commented May 11, 2022

Further cleaned things up and added a lot more comments (I chose to err on the side of over-commenting in some places). Some doc comments are still missing or in need of improvement. Again, those can be improved in a dedicated PR.

Copy link
Contributor

@PROMETHIA-27 PROMETHIA-27 left a comment

Choose a reason for hiding this comment

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

Looks good! I think this will make further changes here much easier and cleaner.

///
/// #[derive(Reflect)]
/// // Register the custom `Hash` function
/// #[reflect(Hash(get_hash))]
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if it would make sense to add Default as a special trait- it definitely has special behavior, and allowing a special custom function for it like this would be nice.
Outside of the scope of this PR, but occurred to me nonetheless.

Copy link
Member Author

Choose a reason for hiding this comment

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

That makes sense to me. It would involve adding a Reflect::reflect_default() method or something though. "Special traits" aren't stored as type data like other traits (can't access it via the registry). So we either add that method to Reflect and remove ReflectDefault or we make all "special traits" accessible from the registry as well.

But yeah that's probably best for a follow up PR (maybe even in #4140).

Copy link
Member

Choose a reason for hiding this comment

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

I like this in #4140 at first glance.

Copy link
Contributor

@PROMETHIA-27 PROMETHIA-27 left a comment

Choose a reason for hiding this comment

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

Oh no! I just noticed the bug I originally added the path attribute to fix is back. These changes seem to fix it; I don't know if they'd break something else, but these lines don't seem necessary.

EDIT: To elaborate, the bug is caused by use-ing anything that starts with #bevy_reflect_path, because that path could be ambiguous during doc-tests... somehow. To fix this I remove any superfluous uses, and if I actually need the Struct trait in scope, instead I switch to UFCS and do #bevy_reflect_path::Struct::{method name}(self, parameters), which does not cause the bug.

crates/bevy_reflect/bevy_reflect_derive/src/impls.rs Outdated Show resolved Hide resolved
@MrGVSV
Copy link
Member Author

MrGVSV commented May 11, 2022

Oh no! I just noticed the bug I originally added the path attribute to fix is back. These changes seem to fix it; I don't know if they'd break something else, but these lines don't seem necessary.

EDIT: To elaborate, the bug is caused by use-ing anything that starts with #bevy_reflect_path, because that path could be ambiguous during doc-tests... somehow. To fix this I remove any superfluous uses, and if I actually need the Struct trait in scope, instead I switch to UFCS and do #bevy_reflect_path::Struct::{method name}(self, parameters), which does not cause the bug.

Thank you! I was scratching my head trying figure out what regressed from your PR to this one. I'll implement the fix tonight.

Now the order is: derive macros, attribute macros, then standard proc
macros.
Removed all usages of `use #bevy_reflect_path`
@alice-i-cecile alice-i-cecile added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label May 12, 2022
@alice-i-cecile
Copy link
Member

bors r+

bors bot pushed a commit that referenced this pull request May 12, 2022
# Objective

The `bevy_reflect_derive` crate is not the cleanest or easiest to follow/maintain. The `lib.rs` file is especially difficult with over 1000 lines of code written in a confusing order. This is just a result of growth within the crate and it would be nice to clean it up for future work.

## Solution

Split `bevy_reflect_derive` into many more submodules. The submodules include:

* `container_attributes` - Code relating to container attributes
* `derive_data` - Code relating to reflection-based derive metadata
* `field_attributes` - Code relating to field attributes
* `impls` - Code containing actual reflection implementations
* `reflect_value` - Code relating to reflection-based value metadata
* `registration` - Code relating to type registration
* `utility` - General-purpose utility functions

This leaves the `lib.rs` file to contain only the public macros, making it much easier to digest (and fewer than 200 lines).

By breaking up the code into smaller modules, we make it easier for future contributors to find the code they're looking for or identify which module best fits their own additions.

### Metadata Structs

This cleanup also adds two big metadata structs: `ReflectFieldAttr` and `ReflectDeriveData`. The former is used to store all attributes for a struct field (if any). The latter is used to store all metadata for struct-based derive inputs.

Both significantly reduce code duplication and make editing these macros much simpler. The tradeoff is that we may collect more metadata than needed. However, this is usually a small thing (such as checking for attributes when they're not really needed or creating a `ReflectFieldAttr` for every field regardless of whether they actually have an attribute).

We could try to remove these tradeoffs and squeeze some more performance out, but doing so might come at the cost of developer experience. Personally, I think it's much nicer to create a `ReflectFieldAttr` for every field since it means I don't have to do two `Option` checks. Others may disagree, though, and so we can discuss changing this either in this PR or in a future one.

### Out of Scope

_Some_ documentation has been added or improved, but ultimately good docs are probably best saved for a dedicated PR.

## 🔍 Focus Points (for reviewers)

I know it's a lot to sift through, so here is a list of **key points for reviewers**:

- The following files contain code that was mostly just relocated:
  - `reflect_value.rs`
  - `registration.rs`
- `container_attributes.rs` was also mostly moved but features some general cleanup (reducing nesting, removing hardcoded strings, etc.) and lots of doc comments
- Most impl logic was moved from `lib.rs` to `impls.rs`, but they have been significantly modified to use the new `ReflectDeriveData` metadata struct in order to reduce duplication.
- `derive_data.rs` and `field_attributes.rs` contain almost entirely new code and should probably be given the most attention.
- Likewise, `from_reflect.rs` saw major changes using `ReflectDeriveData` so it should also be given focus.
- There was no change to the `lib.rs` exports so the end-user API should be the same.

## Prior Work

This task was initially tackled by @NathanSWard in #2377 (which was closed in favor of this PR), so hats off to them for beating me to the punch by nearly a year!

---

## Changelog

* **[INTERNAL]** Split `bevy_reflect_derive` into smaller submodules
* **[INTERNAL]** Add `ReflectFieldAttr`
* **[INTERNAL]** Add `ReflectDeriveData`
* Add `BevyManifest::get_path_direct()` method (`bevy_macro_utils`)


Co-authored-by: MrGVSV <49806985+MrGVSV@users.noreply.github.com>
@bors bors bot changed the title bevy_reflect_derive: Tidying up the code [Merged by Bors] - bevy_reflect_derive: Tidying up the code May 12, 2022
@bors bors bot closed this May 12, 2022
robtfm pushed a commit to robtfm/bevy that referenced this pull request May 17, 2022
# Objective

The `bevy_reflect_derive` crate is not the cleanest or easiest to follow/maintain. The `lib.rs` file is especially difficult with over 1000 lines of code written in a confusing order. This is just a result of growth within the crate and it would be nice to clean it up for future work.

## Solution

Split `bevy_reflect_derive` into many more submodules. The submodules include:

* `container_attributes` - Code relating to container attributes
* `derive_data` - Code relating to reflection-based derive metadata
* `field_attributes` - Code relating to field attributes
* `impls` - Code containing actual reflection implementations
* `reflect_value` - Code relating to reflection-based value metadata
* `registration` - Code relating to type registration
* `utility` - General-purpose utility functions

This leaves the `lib.rs` file to contain only the public macros, making it much easier to digest (and fewer than 200 lines).

By breaking up the code into smaller modules, we make it easier for future contributors to find the code they're looking for or identify which module best fits their own additions.

### Metadata Structs

This cleanup also adds two big metadata structs: `ReflectFieldAttr` and `ReflectDeriveData`. The former is used to store all attributes for a struct field (if any). The latter is used to store all metadata for struct-based derive inputs.

Both significantly reduce code duplication and make editing these macros much simpler. The tradeoff is that we may collect more metadata than needed. However, this is usually a small thing (such as checking for attributes when they're not really needed or creating a `ReflectFieldAttr` for every field regardless of whether they actually have an attribute).

We could try to remove these tradeoffs and squeeze some more performance out, but doing so might come at the cost of developer experience. Personally, I think it's much nicer to create a `ReflectFieldAttr` for every field since it means I don't have to do two `Option` checks. Others may disagree, though, and so we can discuss changing this either in this PR or in a future one.

### Out of Scope

_Some_ documentation has been added or improved, but ultimately good docs are probably best saved for a dedicated PR.

## 🔍 Focus Points (for reviewers)

I know it's a lot to sift through, so here is a list of **key points for reviewers**:

- The following files contain code that was mostly just relocated:
  - `reflect_value.rs`
  - `registration.rs`
- `container_attributes.rs` was also mostly moved but features some general cleanup (reducing nesting, removing hardcoded strings, etc.) and lots of doc comments
- Most impl logic was moved from `lib.rs` to `impls.rs`, but they have been significantly modified to use the new `ReflectDeriveData` metadata struct in order to reduce duplication.
- `derive_data.rs` and `field_attributes.rs` contain almost entirely new code and should probably be given the most attention.
- Likewise, `from_reflect.rs` saw major changes using `ReflectDeriveData` so it should also be given focus.
- There was no change to the `lib.rs` exports so the end-user API should be the same.

## Prior Work

This task was initially tackled by @NathanSWard in bevyengine#2377 (which was closed in favor of this PR), so hats off to them for beating me to the punch by nearly a year!

---

## Changelog

* **[INTERNAL]** Split `bevy_reflect_derive` into smaller submodules
* **[INTERNAL]** Add `ReflectFieldAttr`
* **[INTERNAL]** Add `ReflectDeriveData`
* Add `BevyManifest::get_path_direct()` method (`bevy_macro_utils`)


Co-authored-by: MrGVSV <49806985+MrGVSV@users.noreply.github.com>
bors bot pushed a commit that referenced this pull request May 18, 2022
# Objective

Quick followup to #4712.

While updating some [other PRs](#4218), I realized the `ReflectTraits` struct could be improved. The issue with the current implementation is that `ReflectTraits::get_xxx_impl(...)` returns just the _logic_ to the corresponding `Reflect` trait method, rather than the entire function.

This makes it slightly more annoying to manage since the variable names need to be consistent across files. For example, `get_partial_eq_impl` uses a `value` variable. But the name "value" isn't defined in the `get_partial_eq_impl` method, it's defined in three other methods in a completely separate file.

It's not likely to cause any bugs if we keep it as it is since differing variable names will probably just result in a compile error (except in very particular cases). But it would be useful to someone who wanted to edit/add/remove a method.

## Solution

Made `get_hash_impl`, `get_partial_eq_impl` and `get_serialize_impl` return the entire method implementation for `reflect_hash`, `reflect_partial_eq`, and `serializable`, respectively.

As a result of this, those three `Reflect` methods were also given default implementations. This was fairly simple to do since all three could just be made to return `None`.

---

## Changelog

* Small cleanup/refactor to `ReflectTraits` in `bevy_reflect_derive`
* Gave `Reflect::reflect_hash`, `Reflect::reflect_partial_eq`, and `Reflect::serializable` default implementations
exjam pushed a commit to exjam/bevy that referenced this pull request May 22, 2022
# Objective

The `bevy_reflect_derive` crate is not the cleanest or easiest to follow/maintain. The `lib.rs` file is especially difficult with over 1000 lines of code written in a confusing order. This is just a result of growth within the crate and it would be nice to clean it up for future work.

## Solution

Split `bevy_reflect_derive` into many more submodules. The submodules include:

* `container_attributes` - Code relating to container attributes
* `derive_data` - Code relating to reflection-based derive metadata
* `field_attributes` - Code relating to field attributes
* `impls` - Code containing actual reflection implementations
* `reflect_value` - Code relating to reflection-based value metadata
* `registration` - Code relating to type registration
* `utility` - General-purpose utility functions

This leaves the `lib.rs` file to contain only the public macros, making it much easier to digest (and fewer than 200 lines).

By breaking up the code into smaller modules, we make it easier for future contributors to find the code they're looking for or identify which module best fits their own additions.

### Metadata Structs

This cleanup also adds two big metadata structs: `ReflectFieldAttr` and `ReflectDeriveData`. The former is used to store all attributes for a struct field (if any). The latter is used to store all metadata for struct-based derive inputs.

Both significantly reduce code duplication and make editing these macros much simpler. The tradeoff is that we may collect more metadata than needed. However, this is usually a small thing (such as checking for attributes when they're not really needed or creating a `ReflectFieldAttr` for every field regardless of whether they actually have an attribute).

We could try to remove these tradeoffs and squeeze some more performance out, but doing so might come at the cost of developer experience. Personally, I think it's much nicer to create a `ReflectFieldAttr` for every field since it means I don't have to do two `Option` checks. Others may disagree, though, and so we can discuss changing this either in this PR or in a future one.

### Out of Scope

_Some_ documentation has been added or improved, but ultimately good docs are probably best saved for a dedicated PR.

## 🔍 Focus Points (for reviewers)

I know it's a lot to sift through, so here is a list of **key points for reviewers**:

- The following files contain code that was mostly just relocated:
  - `reflect_value.rs`
  - `registration.rs`
- `container_attributes.rs` was also mostly moved but features some general cleanup (reducing nesting, removing hardcoded strings, etc.) and lots of doc comments
- Most impl logic was moved from `lib.rs` to `impls.rs`, but they have been significantly modified to use the new `ReflectDeriveData` metadata struct in order to reduce duplication.
- `derive_data.rs` and `field_attributes.rs` contain almost entirely new code and should probably be given the most attention.
- Likewise, `from_reflect.rs` saw major changes using `ReflectDeriveData` so it should also be given focus.
- There was no change to the `lib.rs` exports so the end-user API should be the same.

## Prior Work

This task was initially tackled by @NathanSWard in bevyengine#2377 (which was closed in favor of this PR), so hats off to them for beating me to the punch by nearly a year!

---

## Changelog

* **[INTERNAL]** Split `bevy_reflect_derive` into smaller submodules
* **[INTERNAL]** Add `ReflectFieldAttr`
* **[INTERNAL]** Add `ReflectDeriveData`
* Add `BevyManifest::get_path_direct()` method (`bevy_macro_utils`)


Co-authored-by: MrGVSV <49806985+MrGVSV@users.noreply.github.com>
exjam pushed a commit to exjam/bevy that referenced this pull request May 22, 2022
…e#4739)

# Objective

Quick followup to bevyengine#4712.

While updating some [other PRs](bevyengine#4218), I realized the `ReflectTraits` struct could be improved. The issue with the current implementation is that `ReflectTraits::get_xxx_impl(...)` returns just the _logic_ to the corresponding `Reflect` trait method, rather than the entire function.

This makes it slightly more annoying to manage since the variable names need to be consistent across files. For example, `get_partial_eq_impl` uses a `value` variable. But the name "value" isn't defined in the `get_partial_eq_impl` method, it's defined in three other methods in a completely separate file.

It's not likely to cause any bugs if we keep it as it is since differing variable names will probably just result in a compile error (except in very particular cases). But it would be useful to someone who wanted to edit/add/remove a method.

## Solution

Made `get_hash_impl`, `get_partial_eq_impl` and `get_serialize_impl` return the entire method implementation for `reflect_hash`, `reflect_partial_eq`, and `serializable`, respectively.

As a result of this, those three `Reflect` methods were also given default implementations. This was fairly simple to do since all three could just be made to return `None`.

---

## Changelog

* Small cleanup/refactor to `ReflectTraits` in `bevy_reflect_derive`
* Gave `Reflect::reflect_hash`, `Reflect::reflect_partial_eq`, and `Reflect::serializable` default implementations
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
# Objective

The `bevy_reflect_derive` crate is not the cleanest or easiest to follow/maintain. The `lib.rs` file is especially difficult with over 1000 lines of code written in a confusing order. This is just a result of growth within the crate and it would be nice to clean it up for future work.

## Solution

Split `bevy_reflect_derive` into many more submodules. The submodules include:

* `container_attributes` - Code relating to container attributes
* `derive_data` - Code relating to reflection-based derive metadata
* `field_attributes` - Code relating to field attributes
* `impls` - Code containing actual reflection implementations
* `reflect_value` - Code relating to reflection-based value metadata
* `registration` - Code relating to type registration
* `utility` - General-purpose utility functions

This leaves the `lib.rs` file to contain only the public macros, making it much easier to digest (and fewer than 200 lines).

By breaking up the code into smaller modules, we make it easier for future contributors to find the code they're looking for or identify which module best fits their own additions.

### Metadata Structs

This cleanup also adds two big metadata structs: `ReflectFieldAttr` and `ReflectDeriveData`. The former is used to store all attributes for a struct field (if any). The latter is used to store all metadata for struct-based derive inputs.

Both significantly reduce code duplication and make editing these macros much simpler. The tradeoff is that we may collect more metadata than needed. However, this is usually a small thing (such as checking for attributes when they're not really needed or creating a `ReflectFieldAttr` for every field regardless of whether they actually have an attribute).

We could try to remove these tradeoffs and squeeze some more performance out, but doing so might come at the cost of developer experience. Personally, I think it's much nicer to create a `ReflectFieldAttr` for every field since it means I don't have to do two `Option` checks. Others may disagree, though, and so we can discuss changing this either in this PR or in a future one.

### Out of Scope

_Some_ documentation has been added or improved, but ultimately good docs are probably best saved for a dedicated PR.

## 🔍 Focus Points (for reviewers)

I know it's a lot to sift through, so here is a list of **key points for reviewers**:

- The following files contain code that was mostly just relocated:
  - `reflect_value.rs`
  - `registration.rs`
- `container_attributes.rs` was also mostly moved but features some general cleanup (reducing nesting, removing hardcoded strings, etc.) and lots of doc comments
- Most impl logic was moved from `lib.rs` to `impls.rs`, but they have been significantly modified to use the new `ReflectDeriveData` metadata struct in order to reduce duplication.
- `derive_data.rs` and `field_attributes.rs` contain almost entirely new code and should probably be given the most attention.
- Likewise, `from_reflect.rs` saw major changes using `ReflectDeriveData` so it should also be given focus.
- There was no change to the `lib.rs` exports so the end-user API should be the same.

## Prior Work

This task was initially tackled by @NathanSWard in bevyengine#2377 (which was closed in favor of this PR), so hats off to them for beating me to the punch by nearly a year!

---

## Changelog

* **[INTERNAL]** Split `bevy_reflect_derive` into smaller submodules
* **[INTERNAL]** Add `ReflectFieldAttr`
* **[INTERNAL]** Add `ReflectDeriveData`
* Add `BevyManifest::get_path_direct()` method (`bevy_macro_utils`)


Co-authored-by: MrGVSV <49806985+MrGVSV@users.noreply.github.com>
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
…e#4739)

# Objective

Quick followup to bevyengine#4712.

While updating some [other PRs](bevyengine#4218), I realized the `ReflectTraits` struct could be improved. The issue with the current implementation is that `ReflectTraits::get_xxx_impl(...)` returns just the _logic_ to the corresponding `Reflect` trait method, rather than the entire function.

This makes it slightly more annoying to manage since the variable names need to be consistent across files. For example, `get_partial_eq_impl` uses a `value` variable. But the name "value" isn't defined in the `get_partial_eq_impl` method, it's defined in three other methods in a completely separate file.

It's not likely to cause any bugs if we keep it as it is since differing variable names will probably just result in a compile error (except in very particular cases). But it would be useful to someone who wanted to edit/add/remove a method.

## Solution

Made `get_hash_impl`, `get_partial_eq_impl` and `get_serialize_impl` return the entire method implementation for `reflect_hash`, `reflect_partial_eq`, and `serializable`, respectively.

As a result of this, those three `Reflect` methods were also given default implementations. This was fairly simple to do since all three could just be made to return `None`.

---

## Changelog

* Small cleanup/refactor to `ReflectTraits` in `bevy_reflect_derive`
* Gave `Reflect::reflect_hash`, `Reflect::reflect_partial_eq`, and `Reflect::serializable` default implementations
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Reflection Runtime information about types C-Code-Quality A section of code that is hard to understand or change S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

5 participants