├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── derive ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src │ ├── config.rs │ ├── discriminant.rs │ ├── lib.rs │ └── repr.rs ├── rust-toolchain.toml ├── src ├── lib.rs └── test-lints.rs └── tests ├── basic.rs ├── derives.rs ├── ui.rs └── ui ├── basic.rs ├── basic.stderr ├── duplicate.rs ├── duplicate.stderr ├── lints.rs ├── lints.stderr ├── negative.rs ├── negative.stderr ├── nonliteral-duplicate.rs ├── nonliteral-duplicate.stderr ├── nonliteral-overflow.rs ├── nonliteral-overflow.stderr ├── options.rs ├── options.stderr ├── overflow.rs ├── overflow.stderr ├── types.rs ├── types.stderr ├── visibility.rs └── visibility.stderr /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | 7 | jobs: 8 | check-formatting: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - run: cargo fmt --all -- --check 13 | 14 | test: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - run: rustup toolchain install 1.72 --profile minimal 19 | - uses: Swatinem/rust-cache@v2 20 | - run: cargo test 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of 9 | experience, education, socio-economic status, nationality, personal appearance, 10 | race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or reject 41 | comments, commits, code, wiki edits, issues, and other contributions that are 42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 43 | contributor for other behaviors that they deem inappropriate, threatening, 44 | offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | This Code of Conduct also applies outside the project spaces when the Project 56 | Steward has a reasonable belief that an individual's behavior may have a 57 | negative impact on the project or its community. 58 | 59 | ## Conflict Resolution 60 | 61 | We do not believe that all conflict is bad; healthy debate and disagreement 62 | often yield positive results. However, it is never okay to be disrespectful or 63 | to engage in behavior that violates the project’s code of conduct. 64 | 65 | If you see someone violating the code of conduct, you are encouraged to address 66 | the behavior directly with those involved. Many issues can be resolved quickly 67 | and easily, and this gives people more control over the outcome of their 68 | dispute. If you are unable to resolve the matter for any reason, or if the 69 | behavior is threatening or harassing, report it. We are dedicated to providing 70 | an environment where participants feel welcome and safe. 71 | 72 | Reports should be directed to [Alyssa Haroldsen](mailto:kupiakos@google.com), the 73 | Project Steward(s) for open-enum. It is the Project Steward’s duty to 74 | receive and address reported violations of the code of conduct. They will then 75 | work with a committee consisting of representatives from the Open Source 76 | Programs Office and the Google Open Source Strategy team. If for any reason you 77 | are uncomfortable reaching out to the Project Steward, please email 78 | opensource@google.com. 79 | 80 | We will investigate every complaint, but you may not receive a direct response. 81 | We will use our discretion in determining when and how to follow up on reported 82 | incidents, which may range from not taking action to permanent expulsion from 83 | the project and project-sponsored spaces. We will notify the accused of the 84 | report and provide them an opportunity to discuss it before any action is taken. 85 | The identity of the reporter will be omitted from the details of the report 86 | supplied to the accused. In potentially harmful situations, such as ongoing 87 | harassment or threats to anyone's safety, we may take action without notice. 88 | 89 | ## Attribution 90 | 91 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 92 | available at 93 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 94 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code Reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google/conduct/). 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "open-enum" 3 | version = "0.5.2" 4 | authors = ["Alyssa Haroldsen "] 5 | description = "An attribute for generating \"open\" fieldless enums, those that accept any integer value, by using a newtype struct and associated constants" 6 | edition = "2021" 7 | license = "Apache-2.0" 8 | repository = "https://github.com/kupiakos/open-enum" 9 | categories = ["no-std", "rust-patterns"] 10 | keywords = ["enum", "open", "integer", "newtype"] 11 | include = ["/src", "/LICENSE", "Cargo.toml"] 12 | 13 | [features] 14 | # This is not named libc because it would require "dep:libc", 15 | # which doesn't work with trybuild: https://github.com/dtolnay/trybuild/issues/171. 16 | libc_ = ["libc", "open-enum-derive/repr_c"] 17 | std = ["open-enum-derive/repr_c"] 18 | 19 | # Needed to test #[deny(missing_docs)], which doesn't trigger in unit or integration tests. 20 | [[bin]] 21 | name = "test_lints" 22 | path = "src/test-lints.rs" 23 | 24 | [dependencies] 25 | open-enum-derive = { path = "derive", version = "=0.5.2" } 26 | libc = { version = "0.2", optional = true } 27 | 28 | [dev-dependencies] 29 | trybuild = "1" 30 | zerocopy = { version = "0.7.11", features = ["derive"] } 31 | 32 | [workspace] 33 | members = ["derive"] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `open-enum` 2 | 3 | Rust enums are _closed_, meaning that the integer value distinguishing an enum, its _discriminant_, 4 | must be one of the variants listed. If the integer value isn't one of those discriminants, it 5 | is considered immediate [undefined behavior][ub]. This is true for enums with and without fields. 6 | This can make working with enums troublesome in high performance code that can't afford premature 7 | runtime checks. 8 | It can also introduce Undefined Behavior at unexpected times if the author is unfamiliar with 9 | the [rules of writing `unsafe` Rust][nomicon]. 10 | 11 | In constrast, C++ [scoped enumerations][cpp-scoped-enum] are _open_, meaning that the enum is a 12 | strongly-typed integer that could hold any value, though with a scoped set of well-known values. 13 | `open-enum` lets you have this in Rust. It turns this enum declaration: 14 | 15 | ``` rust 16 | #[open_enum] 17 | enum Color { 18 | Red, 19 | Green, 20 | Blue, 21 | Orange, 22 | Black, 23 | } 24 | ``` 25 | 26 | into a tuple struct with associated constants: 27 | 28 | ```rust 29 | #[derive(PartialEq, Eq)] // In order to work in `match`. 30 | struct Color(pub u8); // Automatic integer type, can be specified. 31 | 32 | impl Color { 33 | pub const Red: Self = Color(0); 34 | pub const Green: Self = Color(1); 35 | pub const Blue: Self = Color(2); 36 | pub const Orange: Self = Color(3); 37 | pub const Black: Self = Color(4); 38 | } 39 | ``` 40 | 41 | [cpp-scoped-enum]: https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations 42 | [nomicon]: https://doc.rust-lang.org/nomicon/ 43 | [ub]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html 44 | 45 | ## Contributing 46 | 47 | See [`CONTRIBUTING.md`](CONTRIBUTING.md) for details. 48 | 49 | ## License 50 | 51 | Apache 2.0; see [`LICENSE`](LICENSE) for details. 52 | 53 | ## Disclaimer 54 | 55 | This project is not an official Google project. It is not supported by 56 | Google and Google specifically disclaims all warranties as to its quality, 57 | merchantability, or fitness for a particular purpose. 58 | -------------------------------------------------------------------------------- /derive/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "open-enum-derive" 3 | version = "0.5.2" 4 | description = "An attribute for generating \"open\" C-like enums, those that accept any integer value, by using a newtype struct and associated constants" 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/kupiakos/open-enum/tree/main/derive" 8 | categories = ["no-std", "rust-patterns"] 9 | keywords = ["enum", "open", "integer", "newtype"] 10 | include = ["/src", "LICENSE", "Cargo.toml"] 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [features] 16 | # Enable #[repr(C)] open enums 17 | repr_c = [] 18 | 19 | [dependencies] 20 | syn = { version = "2", features = ["full"] } 21 | quote = "1" 22 | proc-macro2 = "1.0.43" 23 | -------------------------------------------------------------------------------- /derive/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /derive/README.md: -------------------------------------------------------------------------------- 1 | # `open-enum-derive` 2 | 3 | This is the proc macro implementation crate for [`open-enum`](https://crates.io/crates/open-enum). 4 | Users should use that instead of this crate directly. 5 | -------------------------------------------------------------------------------- /derive/src/config.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::collections::HashSet; 16 | 17 | use proc_macro2::{Ident, Span}; 18 | use quote::ToTokens; 19 | use syn::{parse::Parse, Error, Token, Visibility}; 20 | 21 | pub struct Config { 22 | pub allow_alias: bool, 23 | pub repr_visibility: Visibility, 24 | } 25 | 26 | impl Parse for Config { 27 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 28 | let mut out = Self { 29 | allow_alias: false, 30 | repr_visibility: Visibility::Public(Token![pub](Span::call_site())), 31 | }; 32 | let mut seen_names = HashSet::new(); 33 | while !input.is_empty() { 34 | let name: Ident = input.parse()?; 35 | let name_string = name.to_token_stream().to_string(); 36 | let has_value = input.peek(Token![=]); 37 | if has_value { 38 | let _eq_token: Token![=] = input.parse()?; 39 | } 40 | match name_string.as_str() { 41 | "allow_alias" => { 42 | if has_value { 43 | let allow_alias: syn::LitBool = input.parse()?; 44 | out.allow_alias = allow_alias.value; 45 | } else { 46 | out.allow_alias = true; 47 | } 48 | } 49 | name_str @ "inner_vis" if !has_value => { 50 | return Err(Error::new( 51 | name.span(), 52 | format!("Option `{name_str}` requires a value"), 53 | )) 54 | } 55 | "inner_vis" => { 56 | out.repr_visibility = input.parse()?; 57 | if matches!(out.repr_visibility, syn::Visibility::Inherited) { 58 | return Err(input.error("Expected visibility")); 59 | } 60 | } 61 | unknown_name => { 62 | return Err(Error::new( 63 | name.span(), 64 | format!("Unknown option `{unknown_name}`"), 65 | )); 66 | } 67 | } 68 | if !input.is_empty() { 69 | let _comma: Token![,] = input.parse()?; 70 | } 71 | if !seen_names.insert(name_string) { 72 | return Err(Error::new( 73 | name.span(), 74 | format!( 75 | "Option `{name}` listed more than once", 76 | name = name.to_token_stream() 77 | ), 78 | )); 79 | } 80 | } 81 | Ok(out) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /derive/src/discriminant.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use proc_macro2::{Literal, TokenStream}; 16 | use quote::{quote, ToTokens}; 17 | use syn::Expr; 18 | 19 | #[derive(Clone)] 20 | pub enum Discriminant { 21 | Literal(i128), 22 | Nonliteral { base: Box, offset: u32 }, 23 | } 24 | 25 | impl Discriminant { 26 | pub fn new(discriminant_expr: Expr) -> syn::Result { 27 | // Positive literal int 28 | if let syn::Expr::Lit(syn::ExprLit { 29 | lit: syn::Lit::Int(lit), 30 | .. 31 | }) = &discriminant_expr 32 | { 33 | return Ok(Discriminant::Literal(lit.base10_parse()?)); 34 | } 35 | 36 | // Negative literal int 37 | if let syn::Expr::Unary(syn::ExprUnary { 38 | op: syn::UnOp::Neg(_), 39 | expr, 40 | .. 41 | }) = &discriminant_expr 42 | { 43 | if let syn::Expr::Lit(syn::ExprLit { 44 | lit: syn::Lit::Int(lit), 45 | .. 46 | }) = &**expr 47 | { 48 | return Ok(Discriminant::Literal(-lit.base10_parse()?)); 49 | } 50 | } 51 | 52 | // Nonliteral expression 53 | Ok(Discriminant::Nonliteral { 54 | base: Box::new(discriminant_expr), 55 | offset: 0, 56 | }) 57 | } 58 | 59 | pub fn next_value(self) -> Option { 60 | Some(match self { 61 | Discriminant::Literal(val) => Discriminant::Literal(val.checked_add(1)?), 62 | Discriminant::Nonliteral { base, offset } => Discriminant::Nonliteral { 63 | base, 64 | offset: offset.checked_add(1)?, 65 | }, 66 | }) 67 | } 68 | } 69 | 70 | impl ToTokens for Discriminant { 71 | fn to_tokens(&self, tokens: &mut TokenStream) { 72 | tokens.extend(match self { 73 | Discriminant::Literal(value) => Literal::i128_unsuffixed(*value).into_token_stream(), 74 | Discriminant::Nonliteral { base, offset } => { 75 | if *offset == 0 { 76 | base.into_token_stream() 77 | } else { 78 | let offset = Literal::u32_unsuffixed(*offset); 79 | quote!(#base + #offset) 80 | } 81 | } 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extern crate proc_macro; 16 | 17 | mod config; 18 | mod discriminant; 19 | mod repr; 20 | 21 | use config::Config; 22 | 23 | use discriminant::Discriminant; 24 | use proc_macro2::{Span, TokenStream}; 25 | use quote::{format_ident, quote, ToTokens}; 26 | use repr::Repr; 27 | use std::collections::HashSet; 28 | use syn::Attribute; 29 | use syn::{ 30 | parse_macro_input, punctuated::Punctuated, spanned::Spanned, Error, Ident, ItemEnum, Visibility, 31 | }; 32 | 33 | /// Sets the span for every token tree in the token stream 34 | fn set_token_stream_span(tokens: TokenStream, span: Span) -> TokenStream { 35 | tokens 36 | .into_iter() 37 | .map(|mut tt| { 38 | tt.set_span(span); 39 | tt 40 | }) 41 | .collect() 42 | } 43 | 44 | /// Checks that there are no duplicate discriminant values. If all variants are literals, return an `Err` so we can have 45 | /// more clear error messages. Otherwise, emit a static check that ensures no duplicates. 46 | fn check_no_alias<'a>( 47 | enum_: &ItemEnum, 48 | variants: impl Iterator + Clone, 49 | ) -> syn::Result { 50 | // If they're all literals, we can give better error messages by checking at proc macro time. 51 | let mut values: HashSet = HashSet::new(); 52 | for (_, variant, span) in variants { 53 | if let &Discriminant::Literal(value) = variant { 54 | if !values.insert(value) { 55 | return Err(Error::new( 56 | span, 57 | format!("discriminant value `{value}` assigned more than once"), 58 | )); 59 | } 60 | } else { 61 | let mut checking_enum = syn::ItemEnum { 62 | ident: format_ident!("_Check{}", enum_.ident), 63 | vis: Visibility::Inherited, 64 | ..enum_.clone() 65 | }; 66 | checking_enum.attrs.retain(|attr| { 67 | matches!( 68 | attr.path().to_token_stream().to_string().as_str(), 69 | "repr" | "allow" | "warn" | "deny" | "forbid" 70 | ) 71 | }); 72 | return Ok(quote!( 73 | #[allow(dead_code)] 74 | #checking_enum 75 | )); 76 | } 77 | } 78 | Ok(TokenStream::default()) 79 | } 80 | 81 | fn emit_debug_impl<'a>( 82 | ident: &Ident, 83 | variants: impl Iterator + Clone, 84 | attrs: impl Iterator> + Clone, 85 | ) -> TokenStream { 86 | let attrs = attrs.map(|attrs| { 87 | // Only allow "#[cfg(...)]" attributes 88 | let iter = attrs.iter().filter(|attr| attr.path().is_ident("cfg")); 89 | quote!(#(#iter)*) 90 | }); 91 | quote!(impl ::core::fmt::Debug for #ident { 92 | fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 93 | #![allow(unreachable_patterns)] 94 | let s = match *self { 95 | #( #attrs Self::#variants => stringify!(#variants), )* 96 | _ => { 97 | return fmt.debug_tuple(stringify!(#ident)).field(&self.0).finish(); 98 | } 99 | }; 100 | fmt.pad(s) 101 | } 102 | }) 103 | } 104 | 105 | fn path_matches_prelude_derive( 106 | got_path: &syn::Path, 107 | expected_path_after_std: &[&'static str], 108 | ) -> bool { 109 | let &[a, b] = expected_path_after_std else { 110 | unimplemented!("checking against stdlib paths with != 2 parts"); 111 | }; 112 | let segments: Vec<&syn::PathSegment> = got_path.segments.iter().collect(); 113 | if segments 114 | .iter() 115 | .any(|segment| !matches!(segment.arguments, syn::PathArguments::None)) 116 | { 117 | return false; 118 | } 119 | match &segments[..] { 120 | // `core::fmt::Debug` or `some_crate::module::Name` 121 | [maybe_core_or_std, maybe_a, maybe_b] => { 122 | (maybe_core_or_std.ident == "core" || maybe_core_or_std.ident == "std") 123 | && maybe_a.ident == a 124 | && maybe_b.ident == b 125 | } 126 | // `fmt::Debug` or `module::Name` 127 | [maybe_a, maybe_b] => { 128 | maybe_a.ident == a && maybe_b.ident == b && got_path.leading_colon.is_none() 129 | } 130 | // `Debug` or `Name`` 131 | [maybe_b] => maybe_b.ident == b && got_path.leading_colon.is_none(), 132 | _ => false, 133 | } 134 | } 135 | 136 | fn open_enum_impl( 137 | enum_: ItemEnum, 138 | Config { 139 | allow_alias, 140 | repr_visibility, 141 | }: Config, 142 | ) -> Result { 143 | // Does the enum define a `#[repr()]`? 144 | let mut struct_attrs: Vec = Vec::with_capacity(enum_.attrs.len() + 5); 145 | struct_attrs.push(quote!(#[allow(clippy::exhaustive_structs)])); 146 | 147 | if !enum_.generics.params.is_empty() { 148 | return Err(Error::new(enum_.generics.span(), "enum cannot be generic")); 149 | } 150 | let mut variants = Vec::with_capacity(enum_.variants.len()); 151 | let mut last_field = Discriminant::Literal(-1); 152 | for variant in &enum_.variants { 153 | if !matches!(variant.fields, syn::Fields::Unit) { 154 | return Err(Error::new(variant.span(), "enum cannot contain fields")); 155 | } 156 | 157 | let (value, value_span) = if let Some((_, discriminant)) = &variant.discriminant { 158 | let span = discriminant.span(); 159 | (Discriminant::new(discriminant.clone())?, span) 160 | } else { 161 | last_field = last_field 162 | .next_value() 163 | .ok_or_else(|| Error::new(variant.span(), "enum discriminant overflowed"))?; 164 | (last_field.clone(), variant.ident.span()) 165 | }; 166 | last_field = value.clone(); 167 | variants.push((&variant.ident, value, value_span, &variant.attrs)) 168 | } 169 | 170 | let mut impl_attrs: Vec = vec![quote!(#[allow(non_upper_case_globals)])]; 171 | let mut explicit_repr: Option = None; 172 | 173 | // To make `match` seamless, derive(PartialEq, Eq) if they aren't already. 174 | let mut extra_derives = vec![quote!(::core::cmp::PartialEq), quote!(::core::cmp::Eq)]; 175 | 176 | let mut make_custom_debug_impl = false; 177 | for attr in &enum_.attrs { 178 | let mut include_in_struct = true; 179 | // Turns out `is_ident` does a `to_string` every time 180 | match attr.path().to_token_stream().to_string().as_str() { 181 | "derive" => { 182 | if let Ok(derive_paths) = 183 | attr.parse_args_with(Punctuated::::parse_terminated) 184 | { 185 | for derive in &derive_paths { 186 | // These derives are treated specially 187 | const PARTIAL_EQ_PATH: &[&str] = &["cmp", "PartialEq"]; 188 | const EQ_PATH: &[&str] = &["cmp", "Eq"]; 189 | const DEBUG_PATH: &[&str] = &["fmt", "Debug"]; 190 | 191 | if path_matches_prelude_derive(derive, PARTIAL_EQ_PATH) 192 | || path_matches_prelude_derive(derive, EQ_PATH) 193 | { 194 | // This derive is always included, exclude it. 195 | continue; 196 | } 197 | if path_matches_prelude_derive(derive, DEBUG_PATH) && !allow_alias { 198 | make_custom_debug_impl = true; 199 | // Don't include this derive since we're generating a special one. 200 | continue; 201 | } 202 | extra_derives.push(derive.to_token_stream()); 203 | } 204 | include_in_struct = false; 205 | } 206 | } 207 | // Copy linting attribute to the impl. 208 | "allow" | "warn" | "deny" | "forbid" => impl_attrs.push(attr.to_token_stream()), 209 | "repr" => { 210 | assert!(explicit_repr.is_none(), "duplicate explicit repr"); 211 | explicit_repr = Some(attr.parse_args()?); 212 | include_in_struct = false; 213 | } 214 | "non_exhaustive" => { 215 | // technically it's exhaustive if the enum covers the full integer range 216 | return Err(Error::new(attr.path().span(), "`non_exhaustive` cannot be applied to an open enum; it is already non-exhaustive")); 217 | } 218 | _ => {} 219 | } 220 | if include_in_struct { 221 | struct_attrs.push(attr.to_token_stream()); 222 | } 223 | } 224 | 225 | // The proper repr to type-check against 226 | let typecheck_repr: Repr = explicit_repr.unwrap_or(Repr::Isize); 227 | 228 | // The actual representation of the value. 229 | let inner_repr = match explicit_repr { 230 | Some(explicit_repr) => { 231 | // If there is an explicit repr, emit #[repr(transparent)]. 232 | struct_attrs.push(quote!(#[repr(transparent)])); 233 | explicit_repr 234 | } 235 | None => { 236 | // If there isn't an explicit repr, determine an appropriate sized integer that will fit. 237 | // Interpret all discriminant expressions as isize. 238 | repr::autodetect_inner_repr(variants.iter().map(|v| &v.1)) 239 | } 240 | }; 241 | 242 | if !extra_derives.is_empty() { 243 | struct_attrs.push(quote!(#[derive(#(#extra_derives),*)])); 244 | } 245 | 246 | let alias_check = if allow_alias { 247 | TokenStream::default() 248 | } else { 249 | check_no_alias(&enum_, variants.iter().map(|(i, v, s, _)| (*i, v, *s)))? 250 | }; 251 | 252 | let syn::ItemEnum { ident, vis, .. } = enum_; 253 | 254 | let debug_impl = if make_custom_debug_impl { 255 | emit_debug_impl( 256 | &ident, 257 | variants.iter().map(|(i, _, _, _)| *i), 258 | variants.iter().map(|(_, _, _, a)| *a), 259 | ) 260 | } else { 261 | TokenStream::default() 262 | }; 263 | 264 | let fields = variants 265 | .into_iter() 266 | .map(|(name, value, value_span, attrs)| { 267 | let mut value = value.into_token_stream(); 268 | value = set_token_stream_span(value, value_span); 269 | let inner = if typecheck_repr == inner_repr { 270 | value 271 | } else { 272 | quote!(::core::convert::identity::<#typecheck_repr>(#value) as #inner_repr) 273 | }; 274 | quote!( 275 | #(#attrs)* 276 | pub const #name: #ident = #ident(#inner); 277 | ) 278 | }); 279 | 280 | Ok(quote! { 281 | #(#struct_attrs)* 282 | #vis struct #ident(#repr_visibility #inner_repr); 283 | 284 | #(#impl_attrs)* 285 | impl #ident { 286 | #( 287 | #fields 288 | )* 289 | } 290 | #debug_impl 291 | #alias_check 292 | }) 293 | } 294 | 295 | #[proc_macro_attribute] 296 | pub fn open_enum( 297 | attrs: proc_macro::TokenStream, 298 | input: proc_macro::TokenStream, 299 | ) -> proc_macro::TokenStream { 300 | let enum_ = parse_macro_input!(input as syn::ItemEnum); 301 | let config = parse_macro_input!(attrs as Config); 302 | open_enum_impl(enum_, config) 303 | .unwrap_or_else(Error::into_compile_error) 304 | .into() 305 | } 306 | 307 | #[cfg(test)] 308 | mod tests { 309 | use super::*; 310 | 311 | #[test] 312 | fn test_path_matches_stdlib_derive() { 313 | const DEBUG_PATH: &[&str] = &["fmt", "Debug"]; 314 | 315 | for success_case in [ 316 | "::core::fmt::Debug", 317 | "::std::fmt::Debug", 318 | "core::fmt::Debug", 319 | "std::fmt::Debug", 320 | "fmt::Debug", 321 | "Debug", 322 | ] { 323 | assert!( 324 | path_matches_prelude_derive(&syn::parse_str(success_case).unwrap(), DEBUG_PATH), 325 | "{success_case}" 326 | ); 327 | } 328 | 329 | for fail_case in [ 330 | "::fmt::Debug", 331 | "::Debug", 332 | "zerocopy::AsBytes", 333 | "::zerocopy::AsBytes", 334 | "PartialEq", 335 | "core::cmp::Eq", 336 | ] { 337 | assert!( 338 | !path_matches_prelude_derive(&syn::parse_str(fail_case).unwrap(), DEBUG_PATH), 339 | "{fail_case}" 340 | ); 341 | } 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /derive/src/repr.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::discriminant::Discriminant; 16 | use proc_macro2::{Ident, TokenStream}; 17 | use quote::ToTokens; 18 | use std::ops::RangeInclusive; 19 | use syn::{parse::Parse, Error}; 20 | 21 | #[derive(Clone, Copy, PartialEq, Eq)] 22 | pub enum Repr { 23 | I8, 24 | U8, 25 | U16, 26 | I16, 27 | U32, 28 | I32, 29 | U64, 30 | I64, 31 | Usize, 32 | Isize, 33 | #[cfg(feature = "repr_c")] 34 | C, 35 | } 36 | 37 | fn range_contains(x: &RangeInclusive, y: &RangeInclusive) -> bool { 38 | x.contains(y.start()) && x.contains(y.end()) 39 | } 40 | 41 | impl Repr { 42 | const REPR_RANGES: &'static [(Repr, RangeInclusive)] = &[ 43 | (Repr::I8, (i8::MIN as i128)..=(i8::MAX as i128)), 44 | (Repr::U8, (u8::MIN as i128)..=(u8::MAX as i128)), 45 | (Repr::I16, (i16::MIN as i128)..=(i16::MAX as i128)), 46 | (Repr::U16, (u16::MIN as i128)..=(u16::MAX as i128)), 47 | (Repr::I32, (i32::MIN as i128)..=(i32::MAX as i128)), 48 | (Repr::U32, (u32::MIN as i128)..=(u32::MAX as i128)), 49 | (Repr::I64, (i64::MIN as i128)..=(i64::MAX as i128)), 50 | (Repr::U64, (u64::MIN as i128)..=(u64::MAX as i128)), 51 | (Repr::Isize, (isize::MIN as i128)..=(isize::MAX as i128)), 52 | (Repr::Usize, (usize::MIN as i128)..=(usize::MAX as i128)), 53 | ]; 54 | 55 | /// Finds the smallest repr that can fit this range, if any. 56 | fn smallest_fitting_repr(range: RangeInclusive) -> Option { 57 | // TODO: perhaps check this logic matches current rustc behavior? 58 | for (repr, repr_range) in Self::REPR_RANGES { 59 | if range_contains(repr_range, &range) { 60 | return Some(*repr); 61 | } 62 | } 63 | None 64 | } 65 | 66 | fn name(self) -> &'static str { 67 | match self { 68 | Repr::I8 => "i8", 69 | Repr::U8 => "u8", 70 | Repr::U16 => "u16", 71 | Repr::I16 => "i16", 72 | Repr::U32 => "u32", 73 | Repr::I32 => "i32", 74 | Repr::U64 => "u64", 75 | Repr::I64 => "i64", 76 | Repr::Usize => "usize", 77 | Repr::Isize => "isize", 78 | #[cfg(feature = "repr_c")] 79 | Repr::C => "C", 80 | } 81 | } 82 | } 83 | 84 | impl ToTokens for Repr { 85 | fn to_tokens(&self, tokens: &mut TokenStream) { 86 | tokens.extend([match self { 87 | // Technically speaking, #[repr(C)] on an enum isn't always `c_int`, 88 | // but those who care can fix it if they need. 89 | #[cfg(feature = "repr_c")] 90 | Repr::C => quote::quote!(::open_enum::__private::c_int), 91 | x => x.name().parse::().unwrap(), 92 | }]) 93 | } 94 | } 95 | 96 | impl Parse for Repr { 97 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 98 | let ident: Ident = input.parse()?; 99 | Ok(match ident.to_string().as_str() { 100 | "i8" => Repr::I8, 101 | "u8" => Repr::U8, 102 | "i16" => Repr::I16, 103 | "u16" => Repr::U16, 104 | "i32" => Repr::I32, 105 | "u32" => Repr::U32, 106 | "i64" => Repr::I64, 107 | "u64" => Repr::U64, 108 | "usize" => Repr::Usize, 109 | "isize" => Repr::Isize, 110 | #[cfg(feature = "repr_c")] 111 | "C" => Repr::C, 112 | #[cfg(not(feature = "repr_c"))] 113 | "C" => { 114 | return Err(Error::new( 115 | ident.span(), 116 | "#[repr(C)] requires either the `std` or `libc_` feature", 117 | )) 118 | } 119 | _ => { 120 | return Err(Error::new( 121 | ident.span(), 122 | format!("unsupported repr `{ident}`"), 123 | )) 124 | } 125 | }) 126 | } 127 | } 128 | 129 | /// Figure out what the internal representation of the enum should be given its variants. 130 | /// 131 | /// If we don't have sufficient info to auto-shrink the internal repr, fallback to isize. 132 | pub fn autodetect_inner_repr<'a>(variants: impl Iterator) -> Repr { 133 | let mut variants = variants.peekable(); 134 | if variants.peek().is_none() { 135 | // TODO: maybe use the unit type for a fieldless open enum without a #[repr]? 136 | return Repr::Isize; 137 | } 138 | let mut min = i128::MAX; 139 | let mut max = i128::MIN; 140 | for value in variants { 141 | match value { 142 | &Discriminant::Literal(value) => { 143 | min = min.min(value); 144 | max = max.max(value); 145 | } 146 | Discriminant::Nonliteral { .. } => { 147 | // No way to do fancy sizing here, fall back to isize. 148 | return Repr::Isize; 149 | } 150 | } 151 | } 152 | Repr::smallest_fitting_repr(min..=max).unwrap_or(Repr::Isize) 153 | } 154 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | 2 | [toolchain] 3 | channel = "1.72" 4 | components = ["rustfmt", "rustc-dev"] 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Rust enums are _closed_, meaning that the integer value distinguishing an enum, its _discriminant_, 16 | //! must be one of the variants listed. If the integer value isn't one of those discriminants, it 17 | //! is considered immediate [undefined behavior][ub]. This is true for enums with and without fields. 18 | //! 19 | //! This has some disadvantages: 20 | //! - in constrained environments, closed enums can require premature runtime checks when using 21 | //! `TryFrom` to convert from an integer. This is doubly true if the value will be checked again 22 | //! at a later point, such as with a C library. 23 | //! - an outdated binary using an enum won't preserve the value of an unknown field when reserializing 24 | //! data without an extra `Unrecognized` value making the type more expensive than an integer. 25 | //! - it can introduce Undefined Behavior at unexpected times if the author is unfamiliar with 26 | //! the [rules of writing `unsafe` Rust][nomicon]. 27 | //! 28 | //! In constrast, C++ [scoped enumerations][cpp-scoped-enum] are _open_, meaning that the enum is a 29 | //! strongly-typed integer that could hold any value, though with a scoped set of well-known values. 30 | //! 31 | //! The _open enum_ pattern lets you have this in Rust. With a [newtype][newtype] and associated constants, 32 | //! the [open_enum][open_enum] macro turns this enum declaration: 33 | //! 34 | //! ``` 35 | //! # use open_enum::open_enum; 36 | //! #[open_enum] 37 | //! enum Color { 38 | //! Red, 39 | //! Green, 40 | //! Blue, 41 | //! Orange, 42 | //! Black, 43 | //! } 44 | //! ``` 45 | //! 46 | //! into a tuple struct with associated constants: 47 | //! 48 | //! ``` 49 | //! #[derive(PartialEq, Eq)] // In order to work in `match`. 50 | //! struct Color(pub i8); // Automatic integer type, can be specified with #[repr] 51 | //! 52 | //! impl Color { 53 | //! pub const Red: Self = Color(0); 54 | //! pub const Green: Self = Color(1); 55 | //! pub const Blue: Self = Color(2); 56 | //! pub const Orange: Self = Color(3); 57 | //! pub const Black: Self = Color(4); 58 | //! } 59 | //! ``` 60 | //! 61 | //! There are clear readability benefits to using field-less `enum`s to represent enumerated integer data. 62 | //! It provides more type safety than a raw integer, the `enum` syntax is consise, and it provides a 63 | //! set of constants grouped under a type that can have methods. 64 | //! 65 | //! # Usage 66 | //! Usage is similar to regular `enum`s, but with some key differences. 67 | //! 68 | //! ``` 69 | //! # use open_enum::open_enum; 70 | //! # #[open_enum] 71 | //! # #[derive(Debug)] 72 | //! # enum Color { 73 | //! # Red, 74 | //! # Green, 75 | //! # Blue, 76 | //! # Orange, 77 | //! # Black, 78 | //! # } 79 | //! // Construct an open enum with the same `EnumName::VariantName` syntax. 80 | //! let mut blood_of_angry_men = Color::Red; 81 | //! 82 | //! // Access the integer value with `.0`. 83 | //! // This does not work: `Color::Red as u8`. 84 | //! assert_eq!(blood_of_angry_men.0, 0); 85 | //! 86 | //! // Construct an open enum with an arbitrary integer value like any tuple struct. 87 | //! let dark_of_ages_past = Color(4); 88 | //! 89 | //! // open enums always implement `PartialEq` and `Eq`, unlike regular enums. 90 | //! assert_eq!(dark_of_ages_past, Color::Black); 91 | //! 92 | //! // This is outside of the known colors - but that's OK! 93 | //! let this_is_fine = Color(10); 94 | //! 95 | //! // A match is always non-exhaustive - requiring a wildcard branch. 96 | //! match this_is_fine { 97 | //! Color::Red => panic!("a world about to dawn"), 98 | //! Color::Green => panic!("grass"), 99 | //! Color::Blue => panic!("蒼: not to be confused with 緑"), 100 | //! Color::Orange => panic!("fun fact: the fruit name came first"), 101 | //! Color::Black => panic!("the night that ends at last"), 102 | //! // Wildcard branch, if we don't recognize the value. `x =>` also works. 103 | //! Color(value) => assert_eq!(value, 10), 104 | //! } 105 | //! 106 | //! // Unlike a regular enum, you can pass the discriminant as a reference. 107 | //! fn increment(x: &mut i8) { 108 | //! *x += 1; 109 | //! } 110 | //! 111 | //! increment(&mut blood_of_angry_men.0); 112 | //! // These aren't men, they're skinks! 113 | //! assert_eq!(blood_of_angry_men, Color::Green); 114 | //! 115 | //! ``` 116 | //! 117 | //! ## Integer type 118 | //! `open_enum` will automatically determine an appropriately sized integer[^its-all-isize] to 119 | //! represent the enum, if possible[^nonliterals-are-hard]. To choose a specific representation, it's the same 120 | //! as a regular `enum`: add `#[repr(type)]`. 121 | //! You can also specify `#[repr(C)]` to choose a C `int`.[^repr-c-feature][^repr-c-weird] 122 | //! 123 | //! If you specify an explicit `repr`, the output struct will be `#[repr(transparent)]`. 124 | //! 125 | //! ``` 126 | //! # use open_enum::open_enum; 127 | //! #[open_enum] 128 | //! #[repr(i16)] 129 | //! #[derive(Debug)] 130 | //! enum Fruit { 131 | //! Apple, 132 | //! Banana, 133 | //! Kumquat, 134 | //! Orange, 135 | //! } 136 | //! 137 | //! assert_eq!(Fruit::Banana.0, 1i16); 138 | //! assert_eq!(Fruit::Kumquat, Fruit(2)); 139 | //! 140 | //! ``` 141 | //!
142 | //!
143 | //!  **Warning**: `open_enum` may change the automatic integer representation for a given enum
144 | //! in a future version with a minor version bump - it is not considered a breaking change.
145 | //! Do not depend on this type remaining stable - use an explicit `#[repr]` for stability.
146 | //!
147 | //! 
148 | //! 149 | //! [^its-all-isize]: Like regular `enum`s, the declared discriminant for enums without an explicit `repr` 150 | //! is interpreted as an `isize` regardless of the automatic storage type chosen. 151 | //! 152 | //! [^nonliterals-are-hard]: This optimization fails if the `enum` declares a non-literal constant expression 153 | //! as one of its discriminant values, and falls back to `isize`. To avoid this, specify an explicit `repr`. 154 | //! 155 | //! [^repr-c-weird]: Note that this might not actually be the correct default `enum` size for C on all platforms, 156 | //! since the [compiler could choose something smaller than `int`](https://stackoverflow.com/a/366026). 157 | //! 158 | //! [^repr-c-feature]: This requires either the `std` or `libc_` feature (note the underscore) 159 | //! 160 | //! ## Aliasing variants 161 | //! Regular `enum`s cannot have multiple variants with the same discriminant. 162 | //! However, since `open_enum` produces associated constants, multiple 163 | //! names can represent the same integer value. By default, `open_enum` 164 | //! rejects aliasing variants, but it can be allowed with the `allow_alias` option: 165 | //! 166 | //! ``` 167 | //! # use open_enum::open_enum; 168 | //! #[open_enum(allow_alias)] 169 | //! #[derive(Debug)] 170 | //! enum Character { 171 | //! Viola = 0, 172 | //! Cesario = 0, 173 | //! Sebastian, 174 | //! Orsino, 175 | //! Olivia, 176 | //! Malvolio, 177 | //! } 178 | //! 179 | //! assert_eq!(Character::Viola, Character::Cesario); 180 | //! 181 | //! ``` 182 | //! 183 | //! 184 | //! 185 | //! # Custom debug implementation 186 | //! `open_enum` will generate a debug implementation that mirrors the standard `#[derive(Debug)]` for normal Rust enums 187 | //! by printing the name of the variant rather than the value contained, if the value is a named variant. 188 | //! 189 | //! However, if an enum has `#[open_enum(allow_alias)]` specified, the debug representation will be the numeric value only. 190 | //! 191 | //! For example, this given enum, 192 | //! ``` 193 | //! # use open_enum::open_enum; 194 | //! #[open_enum] 195 | //! #[derive(Debug)] 196 | //! enum Fruit { 197 | //! Apple, 198 | //! Pear, 199 | //! Banana, 200 | //! Blueberry = 5, 201 | //! Raspberry, 202 | //! } 203 | //! ``` 204 | //! 205 | //! will have the following debug implementation emitted: 206 | //! ``` 207 | //! # use open_enum::open_enum; 208 | //! # #[open_enum] 209 | //! # enum Fruit { 210 | //! # Apple, 211 | //! # Pear, 212 | //! # Banana, 213 | //! # Blueberry = 5, 214 | //! # Raspberry, 215 | //! # } 216 | //! # impl ::core::fmt::Debug for Fruit { 217 | //! fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 218 | //! #![allow(unreachable_patterns)] 219 | //! let s = match *self { 220 | //! Self::Apple => stringify!(Apple), 221 | //! Self::Pear => stringify!(Pear), 222 | //! Self::Banana => stringify!(Banana), 223 | //! Self::Blueberry => stringify!(Blueberry), 224 | //! Self::Raspberry => stringify!(Raspberry), 225 | //! _ => { 226 | //! return fmt.debug_tuple(stringify!(Fruit)).field(&self.0).finish(); 227 | //! } 228 | //! }; 229 | //! fmt.pad(s) 230 | //! } 231 | //! # } 232 | //! ``` 233 | //! 234 | //! # Compared with `#[non_exhuastive]` 235 | //! The [`non_exhaustive`][non-exhaustive] attribute indicates that a type or variant 236 | //! may have more fields or variants added in the future. When applied to an `enum` (not its variants), 237 | //! it requires that foreign crates provide a wildcard arm when `match`ing. 238 | //! Since open enums are inherently non-exhaustive[^mostly-non-exhaustive], this attribute is incompatible 239 | //! with `open_enum`. Unlike `non_exhaustive`, open enums also require a wildcard branch on `match`es in 240 | //! the defining crate. 241 | //! 242 | //! [^mostly-non-exhaustive]: Unless the enum defines a variant for every value of its underlying integer. 243 | //! 244 | //! # Disadvantages of open enums 245 | //! - The kind listed in the source code, an `enum`, is not the same as the actual output, a `struct`, 246 | //! which could be confusing or hard to debug, since its usage is similar, but not exactly the same. 247 | //! - No niche optimization: `Option` is 1 byte as a regular enum, 248 | //! but 2 bytes as an open enum. 249 | //! - No pattern-matching assistance in rust-analyzer. 250 | //! - You must have a wildcard case when pattern matching. 251 | //! - `match`es that exist elsewhere won't break when you add a new variant, 252 | //! similar to `#[non_exhaustive]`. However, it also means you may accidentally 253 | //! forget to fill out a branch arm. 254 | //! 255 | //! 256 | //! [cpp-scoped-enum]: https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations 257 | //! [nomicon]: https://doc.rust-lang.org/nomicon/ 258 | //! [non-exhaustive]: https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute 259 | //! [ub]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html 260 | 261 | #![no_std] 262 | 263 | /// Constructs an *open* enum from a Rust enum definition, 264 | /// allowing it to represent more than just its listed variants. 265 | /// 266 | /// See the [crate documentation](crate) for more details. 267 | /// 268 | /// # Example 269 | /// ``` 270 | /// # use open_enum::open_enum; 271 | /// #[open_enum] 272 | /// #[derive(Debug)] 273 | /// enum Color { 274 | /// Red, 275 | /// Green, 276 | /// Blue, 277 | /// Orange, 278 | /// Black, 279 | /// } 280 | /// 281 | /// assert_eq!(Color::Red, Color(0)); 282 | /// assert_eq!(Color(10).0, 10); 283 | /// ``` 284 | /// 285 | /// # Options 286 | /// - `allow_alias[ = $bool]`: default `false`. Allows duplicate discriminant values for variants. 287 | /// - `inner_vis = $vis`: default `pub`. Specifies the visibility of the inner integer. 288 | /// 289 | /// # Integer type 290 | /// `open_enum` configures the discriminant type by intercepting a `repr` attribute on the enum. 291 | /// If done, the open enum is `#[repr(transparent)]` over the provided integer type. 292 | /// Otherwise, variant discriminants are interpreted as `isize` and an automatic integer type chosen. 293 | /// 294 | /// # `PartialEq`/`Eq` 295 | /// Open enums implement `PartialEq` and `Eq` in order to work in a `match` statement. 296 | pub use open_enum_derive::open_enum; 297 | 298 | /// Utility items only to be used by macros. Do not expect API stability. 299 | #[doc(hidden)] 300 | pub mod __private { 301 | #[cfg(all(feature = "libc", not(feature = "std")))] 302 | pub use libc::c_int; 303 | 304 | #[cfg(feature = "std")] 305 | extern crate std; 306 | 307 | #[cfg(feature = "std")] 308 | pub use std::os::raw::c_int; 309 | } 310 | -------------------------------------------------------------------------------- /src/test-lints.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Tests lints that should compile. 16 | //! 17 | //! Tests (unit or integration) don't trigger missing_docs, so this must be a binary or library. 18 | //! Binary is simpler. Alternatively, this could be part of the ui/compile-fail test fixture. 19 | 20 | /// Tests that basic attributes propagate, like documentation. 21 | pub mod docs { 22 | #![deny(missing_docs)] 23 | use open_enum::open_enum; 24 | 25 | #[open_enum] 26 | /// This struct has documentation. 27 | pub enum ImportantLetters { 28 | /// A is the first letter of the English alphabet. 29 | A, 30 | 31 | /// B is for Bananaphone. 32 | B, 33 | } 34 | } 35 | 36 | /// Tests that allow lints propagate through an open enum definition correctly. 37 | pub mod allow_lint_propagates { 38 | #![deny(missing_docs)] 39 | use open_enum::open_enum; 40 | 41 | // Checks that local lints propagate correctly. 42 | #[open_enum] 43 | #[allow(missing_docs)] 44 | pub enum HasLintTop { 45 | A, 46 | B, 47 | } 48 | 49 | #[allow(missing_docs)] 50 | #[open_enum] 51 | pub enum HasLintBottom { 52 | A, 53 | B, 54 | } 55 | } 56 | 57 | pub mod clippy { 58 | // We should pass this, as this is a newtype. 59 | #![deny(clippy::exhaustive_structs)] 60 | 61 | #[open_enum::open_enum] 62 | pub enum Foo { 63 | Bar, 64 | Baz, 65 | } 66 | } 67 | 68 | pub mod nonliteral { 69 | #![deny(dead_code)] 70 | 71 | #[open_enum::open_enum] 72 | #[derive(PartialEq, Eq)] // for some reason this has to be here to get a dead_code lint to trigger 73 | #[repr(u32)] 74 | pub enum Fuzz { 75 | Balls = 1 << 1, 76 | } 77 | } 78 | 79 | fn main() {} 80 | -------------------------------------------------------------------------------- /tests/basic.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extern crate open_enum; 16 | use open_enum::*; 17 | 18 | #[open_enum] 19 | #[derive(Debug)] 20 | enum Fruit { 21 | Apple, 22 | Pear, 23 | Banana, 24 | Blueberry = 5, 25 | Raspberry, 26 | } 27 | 28 | #[open_enum] 29 | #[derive(PartialEq, Eq)] 30 | enum AlreadyDerivesEq { 31 | Fizz, 32 | Buzz, 33 | } 34 | 35 | #[open_enum] 36 | #[derive(Hash)] 37 | enum HasHash { 38 | Lemon, 39 | } 40 | 41 | #[open_enum] 42 | #[repr(i32)] 43 | enum ExplicitRepr { 44 | Blah, 45 | Boo, 46 | } 47 | 48 | #[repr(u64)] 49 | #[open_enum] 50 | enum ExplicitReprTop { 51 | Blah, 52 | Boo, 53 | } 54 | 55 | #[open_enum] 56 | enum NegativeDiscriminant { 57 | What = -5, 58 | The = -2, 59 | Minus, 60 | } 61 | 62 | #[open_enum(allow_alias)] 63 | enum Color { 64 | Red = 0, 65 | Blue, 66 | Crimson = 0, 67 | Azure, 68 | } 69 | 70 | #[open_enum(allow_alias = true, inner_vis = pub(crate))] 71 | enum Color2 { 72 | Green = 0, 73 | Purple, 74 | Emerald = 0, 75 | Violet, 76 | } 77 | 78 | #[test] 79 | fn values() { 80 | assert_eq!(Fruit::Apple.0, 0); 81 | assert_eq!(Fruit::Pear.0, 1); 82 | assert_eq!(Fruit::Banana.0, 2); 83 | assert_eq!(Fruit::Blueberry.0, 5); 84 | assert_eq!(Fruit::Raspberry.0, 6); 85 | 86 | assert_eq!(AlreadyDerivesEq::Fizz.0, 0); 87 | assert_eq!(AlreadyDerivesEq::Buzz.0, 1); 88 | 89 | assert_eq!(ExplicitRepr::Blah.0, 0); 90 | assert_eq!(ExplicitRepr::Boo.0, 1); 91 | 92 | assert_eq!(NegativeDiscriminant::What.0, -5); 93 | assert_eq!(NegativeDiscriminant::The.0, -2); 94 | assert_eq!(NegativeDiscriminant::Minus.0, -1); 95 | 96 | assert_eq!(Color::Red.0, 0); 97 | assert_eq!(Color::Crimson.0, 0); 98 | assert_eq!(Color::Blue.0, 1); 99 | assert_eq!(Color::Azure.0, 1); 100 | } 101 | 102 | #[test] 103 | fn match_() { 104 | let _f = Fruit::Blueberry; 105 | for (fruit, expected) in [(Fruit::Apple, "apple"), (Fruit(20), "unknown fruit 20")] { 106 | let fruit_str = match fruit { 107 | Fruit::Apple => "apple".to_string(), 108 | Fruit::Pear => "pear".to_string(), 109 | Fruit::Banana => "banana".to_string(), 110 | Fruit::Blueberry => "blueberry".to_string(), 111 | Fruit::Raspberry => "raspberry".to_string(), 112 | Fruit(x) => format!("unknown fruit {x}"), 113 | }; 114 | assert_eq!(fruit_str, expected); 115 | } 116 | } 117 | 118 | #[test] 119 | fn named_debug() { 120 | let fruits = [ 121 | Fruit::Apple, 122 | Fruit::Banana, 123 | Fruit::Blueberry, 124 | Fruit::Pear, 125 | Fruit::Raspberry, 126 | Fruit(20), 127 | ]; 128 | let expected_output = [ 129 | "Apple", 130 | "Banana", 131 | "Blueberry", 132 | "Pear", 133 | "Raspberry", 134 | "Fruit(20)", 135 | ]; 136 | 137 | for (debug, expected) in fruits 138 | .iter() 139 | .map(|f| format!("{f:?}")) 140 | .zip(expected_output.iter()) 141 | { 142 | assert_eq!(&debug.as_str(), expected) 143 | } 144 | } 145 | 146 | #[test] 147 | fn explicit_repr() { 148 | let _x: i32 = ExplicitRepr::Blah.0; 149 | let _y: u64 = ExplicitReprTop::Blah.0; 150 | } 151 | 152 | #[test] 153 | fn other_derive() { 154 | use std::collections::hash_map::DefaultHasher; 155 | use std::hash::Hash; 156 | let mut hasher = DefaultHasher::new(); 157 | HasHash::Lemon.hash(&mut hasher); 158 | HasHash(20).hash(&mut hasher); 159 | } 160 | 161 | #[cfg(any(feature = "libc", feature = "std"))] 162 | #[test] 163 | fn repr_c() { 164 | use core::mem::size_of; 165 | 166 | #[repr(C)] 167 | #[open_enum] 168 | enum AnimalSound { 169 | Moo, 170 | Honk, 171 | Bahhh, 172 | } 173 | 174 | assert_eq!(AnimalSound::Moo.0, 0); 175 | assert_eq!( 176 | size_of::(), 177 | size_of::() 178 | ); 179 | } 180 | 181 | #[test] 182 | fn pub_() { 183 | mod internal { 184 | use super::open_enum; 185 | 186 | #[open_enum] 187 | pub enum Foo { 188 | Bar, 189 | } 190 | } 191 | assert_eq!(internal::Foo::Bar.0, 0); 192 | } 193 | 194 | #[test] 195 | fn empty_enum() { 196 | #[open_enum] 197 | #[repr(u64)] 198 | enum EmptyExplicitRepr {} 199 | 200 | assert_eq!(EmptyExplicitRepr(10).0, 10u64); 201 | 202 | #[open_enum] 203 | enum Empty {} 204 | 205 | // Current impl falls back to `isize` - this is not guaranteed. 206 | assert_eq!(Empty(10).0, 10); 207 | } 208 | -------------------------------------------------------------------------------- /tests/derives.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Microsoft Corporation 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extern crate open_enum; 16 | use open_enum::*; 17 | 18 | #[open_enum] 19 | #[derive( 20 | Debug, Clone, Copy, PartialEq, Eq, zerocopy::AsBytes, zerocopy::FromBytes, zerocopy::FromZeroes, 21 | )] 22 | #[repr(u32)] 23 | pub enum Color { 24 | Red = 1, 25 | Blue = 2, 26 | } 27 | 28 | #[open_enum] 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 30 | #[repr(u32)] 31 | pub enum ColorWithFeatures { 32 | Red = 1, 33 | Blue = 2, 34 | /// Test doc 35 | #[cfg(feature = "orange")] 36 | Orange = 3, 37 | } 38 | 39 | #[open_enum] 40 | #[derive( 41 | core::fmt::Debug, 42 | std::clone::Clone, 43 | ::core::marker::Copy, 44 | std::cmp::PartialEq, 45 | ::core::cmp::Eq, 46 | zerocopy::AsBytes, 47 | ::zerocopy::FromBytes, 48 | zerocopy::FromZeroes, 49 | )] 50 | #[repr(u32)] 51 | pub enum ColorWithNonPreludeDerives { 52 | Red = 1, 53 | Blue = 2, 54 | } 55 | 56 | // Ensure that `Color` actually implements the `derive`d traits. 57 | #[derive( 58 | Debug, Copy, Clone, PartialEq, Eq, zerocopy::AsBytes, zerocopy::FromBytes, zerocopy::FromZeroes, 59 | )] 60 | #[repr(C)] 61 | pub struct EmbedColor { 62 | pub color: Color, 63 | } 64 | 65 | #[derive( 66 | Debug, Copy, Clone, PartialEq, Eq, zerocopy::AsBytes, zerocopy::FromBytes, zerocopy::FromZeroes, 67 | )] 68 | #[repr(C)] 69 | pub struct EmbedColorWithNonPreludeDerives { 70 | pub color: ColorWithNonPreludeDerives, 71 | } 72 | 73 | #[test] 74 | fn embedded_enum_struct_partialeq() { 75 | assert_eq!( 76 | EmbedColor { color: Color::Red }, 77 | EmbedColor { color: Color::Red } 78 | ); 79 | assert_ne!( 80 | EmbedColor { color: Color::Red }, 81 | EmbedColor { color: Color::Blue } 82 | ); 83 | } 84 | 85 | #[test] 86 | fn embedded_enum_struct_debug() { 87 | let debug_str = format!("{:?}", EmbedColor { color: Color::Red }); 88 | assert!(debug_str.contains("Red"), "{debug_str}"); 89 | } 90 | 91 | #[test] 92 | fn extended_embedded_enum_struct_debug() { 93 | let debug_str = format!( 94 | "{:?}", 95 | EmbedColorWithNonPreludeDerives { 96 | color: ColorWithNonPreludeDerives::Red 97 | } 98 | ); 99 | assert!(debug_str.contains("Red"), "{debug_str}"); 100 | } 101 | -------------------------------------------------------------------------------- /tests/ui.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #[test] 16 | fn ui() { 17 | let t = trybuild::TestCases::new(); 18 | t.compile_fail("tests/ui/*.rs"); 19 | } 20 | -------------------------------------------------------------------------------- /tests/ui/basic.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | extern crate open_enum; 17 | use open_enum::open_enum; 18 | 19 | #[open_enum] 20 | enum Color { 21 | Red, 22 | Blue, 23 | Green(u32), 24 | } 25 | 26 | #[open_enum] 27 | enum Foo { 28 | Bar, 29 | Bin { field: u32 }, 30 | Baz, 31 | } 32 | 33 | 34 | #[open_enum] 35 | #[repr(u128)] 36 | enum A { 37 | A, 38 | B, 39 | C, 40 | } 41 | 42 | #[open_enum] 43 | #[repr(i128)] 44 | enum B { 45 | A, 46 | B, 47 | C 48 | } 49 | 50 | #[open_enum] 51 | enum C { 52 | A, 53 | B, 54 | C 55 | } 56 | 57 | #[open_enum(foo)] 58 | enum D {} 59 | 60 | #[open_enum] 61 | #[non_exhaustive] 62 | enum E { 63 | A = 1, 64 | } 65 | 66 | fn main() {} 67 | -------------------------------------------------------------------------------- /tests/ui/basic.stderr: -------------------------------------------------------------------------------- 1 | error: enum cannot contain fields 2 | --> tests/ui/basic.rs:23:5 3 | | 4 | 23 | Green(u32), 5 | | ^^^^^ 6 | 7 | error: enum cannot contain fields 8 | --> tests/ui/basic.rs:29:5 9 | | 10 | 29 | Bin { field: u32 }, 11 | | ^^^ 12 | 13 | error: unsupported repr `u128` 14 | --> tests/ui/basic.rs:35:8 15 | | 16 | 35 | #[repr(u128)] 17 | | ^^^^ 18 | 19 | error: unsupported repr `i128` 20 | --> tests/ui/basic.rs:43:8 21 | | 22 | 43 | #[repr(i128)] 23 | | ^^^^ 24 | 25 | error: enum cannot be generic 26 | --> tests/ui/basic.rs:51:7 27 | | 28 | 51 | enum C { 29 | | ^ 30 | 31 | error: Unknown option `foo` 32 | --> tests/ui/basic.rs:57:13 33 | | 34 | 57 | #[open_enum(foo)] 35 | | ^^^ 36 | 37 | error: `non_exhaustive` cannot be applied to an open enum; it is already non-exhaustive 38 | --> tests/ui/basic.rs:61:3 39 | | 40 | 61 | #[non_exhaustive] 41 | | ^^^^^^^^^^^^^^ 42 | -------------------------------------------------------------------------------- /tests/ui/duplicate.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extern crate open_enum; 16 | use open_enum::open_enum; 17 | 18 | #[open_enum] 19 | enum DuplicateVariant { 20 | A = 1, 21 | B = 2, 22 | C = 1, 23 | } 24 | 25 | #[open_enum(allow_alias = false)] 26 | enum ImplicitDuplicateVariant { 27 | A = 0, 28 | B = -1, 29 | C, 30 | } 31 | 32 | fn main() {} 33 | -------------------------------------------------------------------------------- /tests/ui/duplicate.stderr: -------------------------------------------------------------------------------- 1 | error: discriminant value `1` assigned more than once 2 | --> tests/ui/duplicate.rs:22:9 3 | | 4 | 22 | C = 1, 5 | | ^ 6 | 7 | error: discriminant value `0` assigned more than once 8 | --> tests/ui/duplicate.rs:29:5 9 | | 10 | 29 | C, 11 | | ^ 12 | -------------------------------------------------------------------------------- /tests/ui/lints.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Tests that failure lints are detected correctly on the 16 | //! output of the proc macro. 17 | #![deny(missing_docs)] 18 | 19 | extern crate open_enum; 20 | 21 | /// Tests that an outer #![deny(missing_docs)] is triggered for an open enum correctly. 22 | pub mod outer_deny_errors { 23 | #![deny(missing_docs)] 24 | use open_enum::open_enum; 25 | 26 | #[open_enum] 27 | pub enum NoDocs { 28 | Round, 29 | Here, 30 | } 31 | } 32 | 33 | /// Tests that the #[deny] lint propagates correctly. 34 | pub mod deny_lint_propagates { 35 | use open_enum::open_enum; 36 | 37 | #[open_enum] 38 | #[deny(missing_docs)] 39 | pub enum NoDocs { 40 | Round, 41 | Here, 42 | } 43 | } 44 | 45 | /// Tests that the #[warn] lint propagates correctly. 46 | pub mod warn_lint_propagates { 47 | use open_enum::open_enum; 48 | 49 | #[warn(missing_docs)] 50 | #[open_enum] 51 | pub enum NoDocs { 52 | Round, 53 | Here, 54 | } 55 | } 56 | 57 | /// Tests that the #[forbid] lint propagates correctly. 58 | pub mod forbid_lint_propagates { 59 | use open_enum::open_enum; 60 | 61 | #[open_enum] 62 | #[forbid(missing_docs)] 63 | pub enum NoDocs { 64 | Round, 65 | Here, 66 | } 67 | } 68 | 69 | fn main() {} 70 | -------------------------------------------------------------------------------- /tests/ui/lints.stderr: -------------------------------------------------------------------------------- 1 | error: missing documentation for a struct 2 | --> tests/ui/lints.rs:26:5 3 | | 4 | 26 | #[open_enum] 5 | | ^^^^^^^^^^^^ 6 | | 7 | note: the lint level is defined here 8 | --> tests/ui/lints.rs:23:13 9 | | 10 | 23 | #![deny(missing_docs)] 11 | | ^^^^^^^^^^^^ 12 | = note: this error originates in the attribute macro `open_enum` (in Nightly builds, run with -Z macro-backtrace for more info) 13 | 14 | error: missing documentation for an associated constant 15 | --> tests/ui/lints.rs:26:5 16 | | 17 | 26 | #[open_enum] 18 | | ^^^^^^^^^^^^ 19 | | 20 | = note: this error originates in the attribute macro `open_enum` (in Nightly builds, run with -Z macro-backtrace for more info) 21 | 22 | error: missing documentation for a struct 23 | --> tests/ui/lints.rs:37:5 24 | | 25 | 37 | #[open_enum] 26 | | ^^^^^^^^^^^^ 27 | | 28 | note: the lint level is defined here 29 | --> tests/ui/lints.rs:38:12 30 | | 31 | 38 | #[deny(missing_docs)] 32 | | ^^^^^^^^^^^^ 33 | = note: this error originates in the attribute macro `open_enum` (in Nightly builds, run with -Z macro-backtrace for more info) 34 | 35 | error: missing documentation for an associated constant 36 | --> tests/ui/lints.rs:37:5 37 | | 38 | 37 | #[open_enum] 39 | | ^^^^^^^^^^^^ 40 | | 41 | = note: this error originates in the attribute macro `open_enum` (in Nightly builds, run with -Z macro-backtrace for more info) 42 | 43 | warning: missing documentation for a struct 44 | --> tests/ui/lints.rs:50:5 45 | | 46 | 50 | #[open_enum] 47 | | ^^^^^^^^^^^^ 48 | | 49 | note: the lint level is defined here 50 | --> tests/ui/lints.rs:49:12 51 | | 52 | 49 | #[warn(missing_docs)] 53 | | ^^^^^^^^^^^^ 54 | = note: this warning originates in the attribute macro `open_enum` (in Nightly builds, run with -Z macro-backtrace for more info) 55 | 56 | warning: missing documentation for an associated constant 57 | --> tests/ui/lints.rs:50:5 58 | | 59 | 50 | #[open_enum] 60 | | ^^^^^^^^^^^^ 61 | | 62 | = note: this warning originates in the attribute macro `open_enum` (in Nightly builds, run with -Z macro-backtrace for more info) 63 | 64 | error: missing documentation for a struct 65 | --> tests/ui/lints.rs:61:5 66 | | 67 | 61 | #[open_enum] 68 | | ^^^^^^^^^^^^ 69 | | 70 | note: the lint level is defined here 71 | --> tests/ui/lints.rs:62:14 72 | | 73 | 62 | #[forbid(missing_docs)] 74 | | ^^^^^^^^^^^^ 75 | = note: this error originates in the attribute macro `open_enum` (in Nightly builds, run with -Z macro-backtrace for more info) 76 | 77 | error: missing documentation for an associated constant 78 | --> tests/ui/lints.rs:61:5 79 | | 80 | 61 | #[open_enum] 81 | | ^^^^^^^^^^^^ 82 | | 83 | = note: this error originates in the attribute macro `open_enum` (in Nightly builds, run with -Z macro-backtrace for more info) 84 | -------------------------------------------------------------------------------- /tests/ui/negative.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extern crate open_enum; 16 | use open_enum::open_enum; 17 | 18 | #[open_enum] 19 | #[repr(u32)] 20 | enum NegativeDiscriminantOnUnsignedValue { 21 | A = 0, 22 | B = -1, 23 | } 24 | 25 | fn main() {} 26 | -------------------------------------------------------------------------------- /tests/ui/negative.stderr: -------------------------------------------------------------------------------- 1 | error[E0600]: cannot apply unary operator `-` to type `u32` 2 | --> tests/ui/negative.rs:22:9 3 | | 4 | 22 | B = -1, 5 | | ^ 6 | | | 7 | | cannot apply unary operator `-` 8 | | help: you may have meant the maximum value of `u32`: `u32::MAX` 9 | | 10 | = note: unsigned values cannot be negated 11 | -------------------------------------------------------------------------------- /tests/ui/nonliteral-duplicate.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extern crate open_enum; 16 | use open_enum::open_enum; 17 | 18 | // This is a separate test case from duplicate.rs since the error messages 19 | // occur at different points of compilation. 20 | 21 | const ONE: isize = 1; 22 | 23 | #[open_enum] 24 | enum NonLiteralDuplicateVariant { 25 | A = 1, 26 | B = 2, 27 | C = ONE, 28 | } 29 | 30 | #[open_enum] 31 | enum NonLiteralImplicitDuplicateVariant { 32 | A = ONE, 33 | B = 0, 34 | C, 35 | } 36 | 37 | fn main() {} 38 | -------------------------------------------------------------------------------- /tests/ui/nonliteral-duplicate.stderr: -------------------------------------------------------------------------------- 1 | error[E0081]: discriminant value `1` assigned more than once 2 | --> tests/ui/nonliteral-duplicate.rs:24:1 3 | | 4 | 24 | enum NonLiteralDuplicateVariant { 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | 25 | A = 1, 7 | | - `1` assigned here 8 | 26 | B = 2, 9 | 27 | C = ONE, 10 | | --- `1` assigned here 11 | 12 | error[E0081]: discriminant value `1` assigned more than once 13 | --> tests/ui/nonliteral-duplicate.rs:31:1 14 | | 15 | 31 | enum NonLiteralImplicitDuplicateVariant { 16 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 17 | 32 | A = ONE, 18 | | --- `1` assigned here 19 | 33 | B = 0, 20 | | - discriminant for `C` incremented from this startpoint (`B` + 1 variant later => `C` = 1) 21 | 34 | C, 22 | | - `1` assigned here 23 | -------------------------------------------------------------------------------- /tests/ui/nonliteral-overflow.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extern crate open_enum; 16 | 17 | #[open_enum::open_enum] 18 | #[repr(u8)] 19 | enum NonLiteralImplicitOverflow { 20 | A = u8::MAX - 1, 21 | B, 22 | C, 23 | } 24 | 25 | // TODO: overflow isize with the implicit repr 26 | fn main() {} 27 | -------------------------------------------------------------------------------- /tests/ui/nonliteral-overflow.stderr: -------------------------------------------------------------------------------- 1 | error[E0370]: enum discriminant overflowed 2 | --> tests/ui/nonliteral-overflow.rs:22:5 3 | | 4 | 22 | C, 5 | | ^ overflowed on value after 255 6 | | 7 | = note: explicitly set `C = 0` if that is desired outcome 8 | -------------------------------------------------------------------------------- /tests/ui/options.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extern crate open_enum; 16 | use open_enum::open_enum; 17 | 18 | #[open_enum(allow_alias = pub(crate))] 19 | pub enum Foo { 20 | Bar, 21 | } 22 | 23 | #[open_enum(not_a_real_option)] 24 | pub enum Fizz { 25 | Buzz, 26 | } 27 | 28 | #[open_enum(inner_vis = "true")] 29 | pub enum Alpha { 30 | Bet, 31 | } 32 | 33 | #[open_enum(allow_alias = true, allow_alias = false)] 34 | pub enum Nine { 35 | Tales, 36 | } 37 | 38 | fn main() {} 39 | -------------------------------------------------------------------------------- /tests/ui/options.stderr: -------------------------------------------------------------------------------- 1 | error: expected boolean literal 2 | --> tests/ui/options.rs:18:27 3 | | 4 | 18 | #[open_enum(allow_alias = pub(crate))] 5 | | ^^^ 6 | 7 | error: Unknown option `not_a_real_option` 8 | --> tests/ui/options.rs:23:13 9 | | 10 | 23 | #[open_enum(not_a_real_option)] 11 | | ^^^^^^^^^^^^^^^^^ 12 | 13 | error: Expected visibility 14 | --> tests/ui/options.rs:28:25 15 | | 16 | 28 | #[open_enum(inner_vis = "true")] 17 | | ^^^^^^ 18 | 19 | error: Option `allow_alias` listed more than once 20 | --> tests/ui/options.rs:33:33 21 | | 22 | 33 | #[open_enum(allow_alias = true, allow_alias = false)] 23 | | ^^^^^^^^^^^ 24 | -------------------------------------------------------------------------------- /tests/ui/overflow.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extern crate open_enum; 16 | 17 | #[open_enum::open_enum] 18 | #[repr(u8)] 19 | enum OverflowingLiteral { 20 | A = 0xffef, 21 | B, 22 | } 23 | 24 | #[open_enum::open_enum] 25 | #[repr(u8)] 26 | enum LiteralImplicitOverflow { 27 | A = 255, 28 | B, 29 | } 30 | 31 | // TODO: overflow isize with the implicit repr 32 | fn main() {} 33 | -------------------------------------------------------------------------------- /tests/ui/overflow.stderr: -------------------------------------------------------------------------------- 1 | error: literal out of range for `u8` 2 | --> tests/ui/overflow.rs:20:9 3 | | 4 | 20 | A = 0xffef, 5 | | ^^^^^^ 6 | | 7 | = note: the literal `0xffef` (decimal `65519`) does not fit into the type `u8` and will become `239u8` 8 | = help: consider using the type `u16` instead 9 | = note: `#[deny(overflowing_literals)]` on by default 10 | 11 | error: literal out of range for `u8` 12 | --> tests/ui/overflow.rs:21:5 13 | | 14 | 21 | B, 15 | | ^ 16 | | 17 | = note: the literal `B` does not fit into the type `u8` whose range is `0..=255` 18 | 19 | error: literal out of range for `u8` 20 | --> tests/ui/overflow.rs:28:5 21 | | 22 | 28 | B, 23 | | ^ 24 | | 25 | = note: the literal `B` does not fit into the type `u8` whose range is `0..=255` 26 | -------------------------------------------------------------------------------- /tests/ui/types.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extern crate open_enum; 16 | use open_enum::open_enum; 17 | 18 | #[open_enum] 19 | enum InterpretsAsIsize { 20 | X = u8::MAX, 21 | } 22 | 23 | fn main() {} 24 | -------------------------------------------------------------------------------- /tests/ui/types.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/ui/types.rs:20:9 3 | | 4 | 20 | X = u8::MAX, 5 | | ^^^^^^^ expected `isize`, found `u8` 6 | -------------------------------------------------------------------------------- /tests/ui/visibility.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | extern crate open_enum; 16 | 17 | pub mod access_limit { 18 | pub mod inner { 19 | #[open_enum::open_enum(inner_vis = pub(super))] 20 | pub enum Foo { 21 | Bar, 22 | Baz, 23 | } 24 | } 25 | const _ShouldCompile: () = assert!(inner::Foo::Bar.0 == 0); 26 | } 27 | 28 | const _ShouldFail: () = assert!(access_limit::inner::Foo::Bar.0 == 0); 29 | 30 | fn main() {} 31 | // const _: 32 | -------------------------------------------------------------------------------- /tests/ui/visibility.stderr: -------------------------------------------------------------------------------- 1 | error[E0616]: field `0` of struct `Foo` is private 2 | --> tests/ui/visibility.rs:28:63 3 | | 4 | 28 | const _ShouldFail: () = assert!(access_limit::inner::Foo::Bar.0 == 0); 5 | | ^ private field 6 | --------------------------------------------------------------------------------