easy-ext-1.0.2/.cargo_vcs_info.json0000644000000001360000000000100125730ustar { "git": { "sha1": "8b284ea2a70025c5748c8dd44066b51cdb5f7407" }, "path_in_vcs": "" }easy-ext-1.0.2/CHANGELOG.md000064400000000000000000000124331046102023000131770ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org). ## [Unreleased] ## [1.0.2] - 2024-05-30 - Fix error with arbitrary self types. ([#42](https://github.com/taiki-e/easy-ext/pull/42), thanks @mbazero) ## [1.0.1] - 2022-09-29 - Fix "patterns aren't allowed in functions without bodies" error when patterns are used in arguments. ## [1.0.0] - 2021-08-24 - Remove deprecated old impl-level visibility syntax (`#[ext(pub)]`). ([#38](https://github.com/taiki-e/easy-ext/pull/38)) Use `pub impl` syntax instead: ```diff - #[ext(pub)] - impl Type { + #[ext] + pub impl Type { fn method(&self) {} } ``` ## [0.2.9] - 2021-07-03 - Fix bug in parsing of where clause. ([#37](https://github.com/taiki-e/easy-ext/pull/37)) ## [0.2.8] - 2021-06-23 **Note:** This release has been yanked because of regression which fixed in 0.2.9. - Support specifying visibility directly on `impl`. ([#31](https://github.com/taiki-e/easy-ext/pull/31)) ```rust #[ext(Ext)] pub impl Type { fn method(&self) {} } ``` ```text pub impl Type { ^^^ ``` The old impl-level visibility syntax (`#[ext(pub)]`) will still be supported, but it is deprecated and will be removed in the next major version. Migration: ```diff - #[ext(pub)] - impl Type { + #[ext] + pub impl Type { fn method(&self) {} } ``` - Improve compile time by removing all dependencies. ([#35](https://github.com/taiki-e/easy-ext/pull/35)) - Support type parameter defaults. ([#32](https://github.com/taiki-e/easy-ext/pull/32)) ## [0.2.7] - 2021-03-25 - Support associated types. ([#26](https://github.com/taiki-e/easy-ext/pull/26)) ## [0.2.6] - 2021-01-19 - Support specifying visibility at impl-level. ([#25](https://github.com/taiki-e/easy-ext/pull/25)) ## [0.2.5] - 2021-01-05 - Exclude unneeded files from crates.io. ## [0.2.4] - 2020-12-29 - Documentation improvements. ## [0.2.3] - 2020-08-24 - [Documentation (`#[doc]` attributes) is now generated only for trait definitions.](https://github.com/taiki-e/easy-ext/pull/23) Previously it generated for both trait definition and trait implementation. See [#20](https://github.com/taiki-e/easy-ext/issues/20) for more details. ## [0.2.2] - 2020-07-22 - Fix `unused_attributes` lint in generated code. ([#22](https://github.com/taiki-e/easy-ext/pull/22)) - Diagnostic improvements. ## [0.2.1] - 2020-07-11 - Documentation improvements. ## [0.2.0] - 2020-04-22 - [`#[ext]` no longer adds type parameter, which is equivalent to `Self`, to the trait's generics.](https://github.com/taiki-e/easy-ext/pull/15) See [#11](https://github.com/taiki-e/easy-ext/issues/11) for more details. ## [0.1.8] - 2020-04-20 - Documentation improvements. ## [0.1.7] - 2020-04-20 - Supported unnamed extension trait. ([#9](https://github.com/taiki-e/easy-ext/pull/9)) ## [0.1.6] - 2019-10-12 - Improved error messages related to visibility. ([#5](https://github.com/taiki-e/easy-ext/pull/5)) ## [0.1.5] - 2019-08-15 - Updated `syn` and `quote` to 1.0. ## [0.1.4] - 2019-03-10 - Updated minimum `syn` version to 0.15.29. ## [0.1.3] - 2019-02-21 - Removed `inline` attributes on trait method side. It can avoid `clippy::inline_fn_without_body` lint by this. ## [0.1.2] - 2019-02-21 - Used `#[allow(patterns_in_fns_without_body)]` to generated extension trait. - Fixed some bugs related to generics. ## [0.1.1] - 2019-02-21 **Note:** This release has been yanked. - Fixed an error related to generics. ## [0.1.0] - 2019-02-20 **Note:** This release has been yanked. Initial release [Unreleased]: https://github.com/taiki-e/easy-ext/compare/v1.0.2...HEAD [1.0.2]: https://github.com/taiki-e/easy-ext/compare/v1.0.1...v1.0.2 [1.0.1]: https://github.com/taiki-e/easy-ext/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/taiki-e/easy-ext/compare/v0.2.9...v1.0.0 [0.2.9]: https://github.com/taiki-e/easy-ext/compare/v0.2.8...v0.2.9 [0.2.8]: https://github.com/taiki-e/easy-ext/compare/v0.2.7...v0.2.8 [0.2.7]: https://github.com/taiki-e/easy-ext/compare/v0.2.6...v0.2.7 [0.2.6]: https://github.com/taiki-e/easy-ext/compare/v0.2.5...v0.2.6 [0.2.5]: https://github.com/taiki-e/easy-ext/compare/v0.2.4...v0.2.5 [0.2.4]: https://github.com/taiki-e/easy-ext/compare/v0.2.3...v0.2.4 [0.2.3]: https://github.com/taiki-e/easy-ext/compare/v0.2.2...v0.2.3 [0.2.2]: https://github.com/taiki-e/easy-ext/compare/v0.2.1...v0.2.2 [0.2.1]: https://github.com/taiki-e/easy-ext/compare/v0.2.0...v0.2.1 [0.2.0]: https://github.com/taiki-e/easy-ext/compare/v0.1.8...v0.2.0 [0.1.8]: https://github.com/taiki-e/easy-ext/compare/v0.1.7...v0.1.8 [0.1.7]: https://github.com/taiki-e/easy-ext/compare/v0.1.6...v0.1.7 [0.1.6]: https://github.com/taiki-e/easy-ext/compare/v0.1.5...v0.1.6 [0.1.5]: https://github.com/taiki-e/easy-ext/compare/v0.1.4...v0.1.5 [0.1.4]: https://github.com/taiki-e/easy-ext/compare/v0.1.3...v0.1.4 [0.1.3]: https://github.com/taiki-e/easy-ext/compare/v0.1.2...v0.1.3 [0.1.2]: https://github.com/taiki-e/easy-ext/compare/v0.1.1...v0.1.2 [0.1.1]: https://github.com/taiki-e/easy-ext/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/taiki-e/easy-ext/releases/tag/v0.1.0 easy-ext-1.0.2/Cargo.toml0000644000000054730000000000100106020ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.31" name = "easy-ext" version = "1.0.2" exclude = [ "/.*", "/tools", ] description = """ A lightweight attribute macro for easily writing extension trait pattern. """ readme = "README.md" keywords = [ "extension", "trait", "macros", "attribute", ] categories = [ "no-std", "no-std::no-alloc", "rust-patterns", ] license = "Apache-2.0 OR MIT" repository = "https://github.com/taiki-e/easy-ext" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [lib] proc-macro = true [dev-dependencies.async-trait] version = "0.1" [dev-dependencies.rustversion] version = "1" [lints.clippy] all = "warn" as_ptr_cast_mut = "warn" default_union_representation = "warn" inline_asm_x86_att_syntax = "warn" pedantic = "warn" trailing_empty_array = "warn" transmute_undefined_repr = "warn" undocumented_unsafe_blocks = "warn" [lints.clippy.bool_assert_comparison] level = "allow" priority = 1 [lints.clippy.borrow_as_ptr] level = "allow" priority = 1 [lints.clippy.declare_interior_mutable_const] level = "allow" priority = 1 [lints.clippy.doc_markdown] level = "allow" priority = 1 [lints.clippy.float_cmp] level = "allow" priority = 1 [lints.clippy.incompatible_msrv] level = "allow" priority = 1 [lints.clippy.lint_groups_priority] level = "allow" priority = 1 [lints.clippy.manual_assert] level = "allow" priority = 1 [lints.clippy.manual_range_contains] level = "allow" priority = 1 [lints.clippy.missing_errors_doc] level = "allow" priority = 1 [lints.clippy.module_name_repetitions] level = "allow" priority = 1 [lints.clippy.nonminimal_bool] level = "allow" priority = 1 [lints.clippy.similar_names] level = "allow" priority = 1 [lints.clippy.single_match] level = "allow" priority = 1 [lints.clippy.single_match_else] level = "allow" priority = 1 [lints.clippy.struct_excessive_bools] level = "allow" priority = 1 [lints.clippy.struct_field_names] level = "allow" priority = 1 [lints.clippy.too_many_arguments] level = "allow" priority = 1 [lints.clippy.too_many_lines] level = "allow" priority = 1 [lints.clippy.type_complexity] level = "allow" priority = 1 [lints.rust] improper_ctypes = "warn" improper_ctypes_definitions = "warn" non_ascii_idents = "warn" rust_2018_idioms = "warn" single_use_lifetimes = "warn" unreachable_pub = "warn" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 easy-ext-1.0.2/Cargo.toml.orig000064400000000000000000000061531046102023000142570ustar 00000000000000[package] name = "easy-ext" version = "1.0.2" #publish:version edition = "2018" rust-version = "1.31" license = "Apache-2.0 OR MIT" repository = "https://github.com/taiki-e/easy-ext" keywords = ["extension", "trait", "macros", "attribute"] categories = ["no-std", "no-std::no-alloc", "rust-patterns"] exclude = ["/.*", "/tools"] description = """ A lightweight attribute macro for easily writing extension trait pattern. """ [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [lib] proc-macro = true [dev-dependencies] async-trait = "0.1" rustversion = "1" trybuild = { git = "https://github.com/taiki-e/trybuild.git", branch = "dev" } # adjust overwrite behavior [lints] workspace = true [workspace] # This table is shared by projects under github.com/taiki-e. # It is not intended for manual editing. [workspace.lints.rust] improper_ctypes = "warn" improper_ctypes_definitions = "warn" non_ascii_idents = "warn" rust_2018_idioms = "warn" single_use_lifetimes = "warn" unexpected_cfgs = { level = "warn", check-cfg = [ ] } unreachable_pub = "warn" # unsafe_op_in_unsafe_fn = "warn" # Set at crate-level instead since https://github.com/rust-lang/rust/pull/100081 is not available on MSRV [workspace.lints.clippy] all = "warn" # Downgrade deny-by-default lints pedantic = "warn" as_ptr_cast_mut = "warn" default_union_representation = "warn" inline_asm_x86_att_syntax = "warn" trailing_empty_array = "warn" transmute_undefined_repr = "warn" undocumented_unsafe_blocks = "warn" # Suppress buggy or noisy clippy lints bool_assert_comparison = { level = "allow", priority = 1 } borrow_as_ptr = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/8286 declare_interior_mutable_const = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/7665 doc_markdown = { level = "allow", priority = 1 } float_cmp = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/7725 incompatible_msrv = { level = "allow", priority = 1 } # buggy: doesn't consider cfg, https://github.com/rust-lang/rust-clippy/issues/12280, https://github.com/rust-lang/rust-clippy/issues/12257#issuecomment-2093667187 lint_groups_priority = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/12270 manual_assert = { level = "allow", priority = 1 } manual_range_contains = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/6455#issuecomment-1225966395 missing_errors_doc = { level = "allow", priority = 1 } module_name_repetitions = { level = "allow", priority = 1 } nonminimal_bool = { level = "allow", priority = 1 } # buggy: https://github.com/rust-lang/rust-clippy/issues?q=is%3Aissue+is%3Aopen+nonminimal_bool similar_names = { level = "allow", priority = 1 } single_match = { level = "allow", priority = 1 } single_match_else = { level = "allow", priority = 1 } struct_excessive_bools = { level = "allow", priority = 1 } struct_field_names = { level = "allow", priority = 1 } too_many_arguments = { level = "allow", priority = 1 } too_many_lines = { level = "allow", priority = 1 } type_complexity = { level = "allow", priority = 1 } easy-ext-1.0.2/LICENSE-APACHE000064400000000000000000000236761046102023000133250ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS easy-ext-1.0.2/LICENSE-MIT000064400000000000000000000017771046102023000130330ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. easy-ext-1.0.2/README.md000064400000000000000000000100641046102023000126430ustar 00000000000000# easy-ext [![crates.io](https://img.shields.io/crates/v/easy-ext?style=flat-square&logo=rust)](https://crates.io/crates/easy-ext) [![docs.rs](https://img.shields.io/badge/docs.rs-easy--ext-blue?style=flat-square&logo=docs.rs)](https://docs.rs/easy-ext) [![license](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue?style=flat-square)](#license) [![msrv](https://img.shields.io/badge/msrv-1.31-blue?style=flat-square&logo=rust)](https://www.rust-lang.org) [![github actions](https://img.shields.io/github/actions/workflow/status/taiki-e/easy-ext/ci.yml?branch=main&style=flat-square&logo=github)](https://github.com/taiki-e/easy-ext/actions) A lightweight attribute macro for easily writing [extension trait pattern][rfc0445]. ```toml [dependencies] easy-ext = "1" ``` ## Examples ```rust use easy_ext::ext; #[ext(ResultExt)] pub impl Result { fn err_into(self) -> Result where E: Into, { self.map_err(Into::into) } } ``` Code like this will be generated: ```rust pub trait ResultExt { fn err_into(self) -> Result where E: Into; } impl ResultExt for Result { fn err_into(self) -> Result where E: Into, { self.map_err(Into::into) } } ``` You can elide the trait name. ```rust use easy_ext::ext; #[ext] impl Result { fn err_into(self) -> Result where E: Into, { self.map_err(Into::into) } } ``` Note that in this case, `#[ext]` assigns a random name, so you cannot import/export the generated trait. ### Visibility There are two ways to specify visibility. #### Impl-level visibility The first way is to specify visibility at the impl level. For example: ```rust use easy_ext::ext; // unnamed #[ext] pub impl str { fn foo(&self) {} } // named #[ext(StrExt)] pub impl str { fn bar(&self) {} } ``` #### Associated-item-level visibility Another way is to specify visibility at the associated item level. For example, if the method is `pub` then the trait will also be `pub`: ```rust use easy_ext::ext; #[ext(ResultExt)] // generate `pub trait ResultExt` impl Result { pub fn err_into(self) -> Result where E: Into, { self.map_err(Into::into) } } ``` This is useful when migrate from an inherent impl to an extension trait. Note that the visibility of all the associated items in the `impl` must be identical. Note that you cannot specify impl-level visibility and associated-item-level visibility at the same time. ### [Supertraits](https://doc.rust-lang.org/reference/items/traits.html#supertraits) If you want the extension trait to be a subtrait of another trait, add `Self: SubTrait` bound to the `where` clause. ```rust use easy_ext::ext; #[ext(Ext)] impl T where Self: Default, { fn method(&self) {} } ``` ### Supported items #### [Associated functions (methods)](https://doc.rust-lang.org/reference/items/associated-items.html#associated-functions-and-methods) ```rust use easy_ext::ext; #[ext] impl T { fn method(&self) {} } ``` #### [Associated constants](https://doc.rust-lang.org/reference/items/associated-items.html#associated-constants) ```rust use easy_ext::ext; #[ext] impl T { const MSG: &'static str = "Hello!"; } ``` #### [Associated types](https://doc.rust-lang.org/reference/items/associated-items.html#associated-types) ```rust use easy_ext::ext; #[ext] impl str { type Owned = String; fn method(&self) -> Self::Owned { self.to_owned() } } ``` [rfc0445]: https://github.com/rust-lang/rfcs/blob/HEAD/text/0445-extension-trait-conventions.md ## License Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT license](LICENSE-MIT) at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. easy-ext-1.0.2/src/ast.rs000064400000000000000000001432471046102023000133220ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT // Based on: // - https://github.com/dtolnay/syn/blob/1.0.70/src/item.rs // - https://github.com/dtolnay/syn/blob/1.0.70/src/generics.rs use std::fmt; use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; #[derive(Clone)] pub(crate) struct Lifetime { pub(crate) apostrophe: Span, pub(crate) ident: Ident, } #[derive(Clone, Default)] pub(crate) struct Generics { pub(crate) lt_token: Option, pub(crate) params: Vec<(GenericParam, Option)>, pub(crate) gt_token: Option, pub(crate) where_clause: Option, } impl Generics { pub(crate) fn make_where_clause(&mut self) -> &mut WhereClause { self.where_clause.get_or_insert_with(|| WhereClause { where_token: Ident::new("where", Span::call_site()), predicates: vec![], }) } pub(crate) fn impl_generics(&self) -> ImplGenerics<'_> { ImplGenerics(self) } pub(crate) fn ty_generics(&self) -> TypeGenerics<'_> { TypeGenerics(self) } } #[derive(Clone)] pub(crate) enum GenericParam { /// A generic type parameter: `T: Into`. Type(TypeParam), /// A lifetime definition: `'a: 'b + 'c + 'd`. Lifetime(LifetimeDef), /// A const generic parameter: `const LENGTH: usize`. Const(ConstParam), } #[derive(Clone)] pub(crate) struct TypeParam { pub(crate) attrs: Vec, pub(crate) ident: Ident, pub(crate) colon_token: Option, pub(crate) bounds: Vec<(TypeParamBound, Option)>, pub(crate) eq_token: Option, pub(crate) default: Option, } #[derive(Clone)] pub(crate) struct TypeParamBound { pub(crate) tokens: TokenStream, pub(crate) is_maybe: bool, } impl TypeParamBound { fn new(tokens: Vec, is_maybe: bool) -> Self { Self { tokens: tokens.into_iter().collect(), is_maybe } } } #[derive(Clone)] pub(crate) struct LifetimeDef { pub(crate) attrs: Vec, pub(crate) lifetime: Lifetime, pub(crate) colon_token: Option, pub(crate) bounds: TokenStream, } #[derive(Clone)] pub(crate) struct BoundLifetimes { pub(crate) for_token: Ident, pub(crate) lt_token: Punct, pub(crate) lifetimes: Vec<(LifetimeDef, Option)>, pub(crate) gt_token: Punct, } #[derive(Clone)] pub(crate) struct ConstParam { pub(crate) attrs: Vec, pub(crate) const_token: Ident, pub(crate) ident: Ident, pub(crate) colon_token: Punct, pub(crate) ty: TokenStream, pub(crate) eq_token: Option, pub(crate) default: Option, } pub(crate) struct ImplGenerics<'a>(&'a Generics); pub(crate) struct TypeGenerics<'a>(&'a Generics); #[derive(Clone)] pub(crate) struct WhereClause { pub(crate) where_token: Ident, pub(crate) predicates: Vec<(WherePredicate, Option)>, } #[derive(Clone)] pub(crate) enum WherePredicate { Type(PredicateType), Lifetime(PredicateLifetime), } #[derive(Clone)] pub(crate) struct PredicateType { pub(crate) lifetimes: Option, pub(crate) bounded_ty: TokenStream, pub(crate) colon_token: Punct, pub(crate) bounds: Vec<(TypeParamBound, Option)>, } #[derive(Clone)] pub(crate) struct PredicateLifetime { pub(crate) lifetime: Lifetime, pub(crate) colon_token: Punct, pub(crate) bounds: Vec<(Lifetime, Option)>, } // Outer attribute #[derive(Clone)] pub(crate) struct Attribute { // `#` pub(crate) pound_token: Punct, // `[...]` pub(crate) tokens: Group, pub(crate) kind: AttributeKind, } #[derive(Clone, Copy, PartialEq)] pub(crate) enum AttributeKind { // #[doc ...] Doc, // #[inline ...] Inline, Other, } impl Attribute { pub(crate) fn new(tokens: Vec) -> Self { Self { pound_token: Punct::new('#', Spacing::Alone), tokens: Group::new(Delimiter::Bracket, tokens.into_iter().collect()), kind: AttributeKind::Other, } } } #[derive(Clone)] pub(crate) enum Visibility { // `pub`. Public(Ident), //`pub(self)`, `pub(super)`, `pub(crate)`, or `pub(in some::module)` Restricted(Ident, Group), Inherited, } impl Visibility { pub(crate) fn is_inherited(&self) -> bool { match self { Visibility::Inherited => true, _ => false, } } } impl PartialEq for Visibility { fn eq(&self, other: &Self) -> bool { match (self, other) { (Visibility::Public(_), Visibility::Public(_)) | (Visibility::Inherited, Visibility::Inherited) => true, (Visibility::Restricted(_, x), Visibility::Restricted(_, y)) => { x.stream().to_string() == y.stream().to_string() } _ => false, } } } impl fmt::Display for Visibility { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Visibility::Public(_) => f.write_str("pub"), Visibility::Inherited => Ok(()), Visibility::Restricted(_, g) => write!(f, "pub{}", g), } } } pub(crate) struct ItemImpl { pub(crate) attrs: Vec, pub(crate) vis: Visibility, pub(crate) defaultness: Option, pub(crate) unsafety: Option, pub(crate) impl_token: Ident, pub(crate) generics: Generics, pub(crate) const_token: Option, pub(crate) trait_: Option<(Ident, TokenStream, Ident)>, pub(crate) self_ty: Vec, pub(crate) brace_token: Span, pub(crate) items: Vec, } pub(crate) enum ImplItem { Const(ImplItemConst), Method(ImplItemMethod), Type(ImplItemType), } pub(crate) struct ImplItemConst { pub(crate) attrs: Vec, pub(crate) vis: Visibility, pub(crate) defaultness: Option, pub(crate) const_token: Ident, pub(crate) ident: Ident, pub(crate) colon_token: Punct, pub(crate) ty: TokenStream, pub(crate) eq_token: Punct, pub(crate) expr: Vec, pub(crate) semi_token: Punct, } pub(crate) struct ImplItemMethod { pub(crate) attrs: Vec, pub(crate) vis: Visibility, pub(crate) defaultness: Option, pub(crate) sig: Signature, pub(crate) body: Group, } pub(crate) struct ImplItemType { pub(crate) attrs: Vec, pub(crate) vis: Visibility, pub(crate) defaultness: Option, pub(crate) type_token: Ident, pub(crate) ident: Ident, pub(crate) generics: Generics, pub(crate) eq_token: Punct, pub(crate) ty: Vec, pub(crate) semi_token: Punct, } #[derive(Clone)] pub(crate) struct Signature { // [const] [async] [unsafe] [extern []] fn pub(crate) before_ident: Vec, pub(crate) ident: Ident, pub(crate) generics: Generics, pub(crate) paren_token: Span, pub(crate) inputs: Vec, pub(crate) output: Option, } #[derive(Clone)] pub(crate) enum FnArg { Receiver(TokenStream, Option), Typed(TokenStream, Punct, TokenStream, Option), } pub(crate) struct ItemTrait { pub(crate) attrs: Vec, pub(crate) vis: Visibility, pub(crate) unsafety: Option, pub(crate) trait_token: Ident, pub(crate) ident: Ident, pub(crate) generics: Generics, pub(crate) brace_token: Span, pub(crate) items: Vec, } pub(crate) enum TraitItem { Const(TraitItemConst), Method(TraitItemMethod), Type(TraitItemType), } pub(crate) struct TraitItemConst { pub(crate) attrs: Vec, pub(crate) const_token: Ident, pub(crate) ident: Ident, pub(crate) colon_token: Punct, pub(crate) ty: TokenStream, pub(crate) semi_token: Punct, } pub(crate) struct TraitItemMethod { pub(crate) attrs: Vec, pub(crate) sig: Signature, pub(crate) semi_token: Punct, } pub(crate) struct TraitItemType { pub(crate) attrs: Vec, pub(crate) type_token: Ident, pub(crate) ident: Ident, pub(crate) generics: Generics, pub(crate) semi_token: Punct, } pub(crate) mod parsing { use std::iter::FromIterator; use proc_macro::{Delimiter, Punct, Spacing, TokenStream, TokenTree}; use super::{ Attribute, AttributeKind, BoundLifetimes, ConstParam, FnArg, GenericParam, Generics, ImplItem, ImplItemConst, ImplItemMethod, ImplItemType, ItemImpl, Lifetime, LifetimeDef, PredicateLifetime, PredicateType, Signature, TypeParam, TypeParamBound, Visibility, WhereClause, WherePredicate, }; use crate::{error::Result, iter::TokenIter, to_tokens::ToTokens}; fn parse_until_punct(input: &mut TokenIter, ch: char) -> Result<(Vec, Punct)> { let mut buf = vec![]; loop { let tt = input.next(); match tt { Some(TokenTree::Punct(ref p)) if p.as_char() == ch && p.spacing() == Spacing::Alone => { if let Some(TokenTree::Punct(p)) = tt { return Ok((buf, p)); } unreachable!(); } None => { // TODO: pass scope span bail!(TokenStream::new(), "expected `{}`", ch); } Some(tt) => buf.push(tt), } } } fn append_tokens_until( input: &mut TokenIter, buf: &mut Vec, visit_first_angle_bracket: bool, f: fn(Option<&TokenTree>) -> bool, ) -> Result<()> { let mut angle_bracket: i32 = 0 - i32::from(visit_first_angle_bracket); loop { let tt = input.peek(); match tt { Some(TokenTree::Punct(p)) if p.as_char() == '<' => { angle_bracket += 1; } Some(TokenTree::Punct(p)) if p.as_char() == '>' => { match buf.last() { Some(TokenTree::Punct(p)) if p.as_char() == '-' && p.spacing() == Spacing::Joint => { // `->` // It's so confusing with `>`, so do not visit it. buf.push(input.next().unwrap()); continue; } _ => {} } angle_bracket -= 1; if angle_bracket >= 0 { buf.push(input.next().unwrap()); continue; } } Some(_) | None => {} } if angle_bracket <= 0 && f(tt) { return Ok(()); } buf.push(input.next().ok_or_else(|| { // TODO: pass scope span format_err!(TokenStream::new(), "unexpected end of input") })?); } } fn parse_attrs(input: &mut TokenIter) -> Result> { let mut attrs = vec![]; while input.peek_t(&'#') { let pound_token = input.parse_punct('#')?; let tokens = input.parse_group(Delimiter::Bracket)?; let mut kind = AttributeKind::Other; let mut iter = TokenIter::new(tokens.stream()); if let Some(TokenTree::Ident(i)) = iter.next() { match iter.next() { // ignore #[path ...] Some(TokenTree::Punct(ref p)) if p.as_char() == ':' => {} _ => match &*i.to_string() { "doc" => kind = AttributeKind::Doc, "inline" => kind = AttributeKind::Inline, _ => {} }, } } let attr = Attribute { pound_token, tokens, kind }; attrs.push(attr); } Ok(attrs) } fn parse_generics(input: &mut TokenIter) -> Result { if !input.peek_t(&'<') { return Ok(Generics::default()); } let lt_token = input.parse_punct('<')?; let mut params = vec![]; loop { if input.peek_t(&'>') { break; } let attrs = parse_attrs(input)?; let value = if input.peek_lifetime() { GenericParam::Lifetime(LifetimeDef { attrs, ..parse_lifetime_def(input)? }) } else if input.peek_t(&"const") { GenericParam::Const(ConstParam { attrs, ..parse_const_param(input)? }) } else if input.peek_t(&"_") { GenericParam::Type(TypeParam { attrs, ident: input.parse_ident()?, colon_token: None, bounds: vec![], eq_token: None, default: None, }) } else if input.peek_ident().is_some() { GenericParam::Type(TypeParam { attrs, ..parse_type_param(input)? }) } else { bail!(input.next(), "expected one of: lifetime, identifier, `const`, `_`"); }; if input.peek_t(&'>') { params.push((value, None)); break; } let punct = input.parse_punct(',')?; params.push((value, Some(punct))); } let gt_token = input.parse_punct('>')?; Ok(Generics { lt_token: Some(lt_token), params, gt_token: Some(gt_token), where_clause: None, }) } fn parse_lifetime(input: &mut TokenIter) -> Result { let tt = input.next(); match &tt { Some(TokenTree::Punct(p)) if p.as_char() == '\'' && p.spacing() == Spacing::Joint => { match input.next() { Some(TokenTree::Ident(ident)) => Ok(Lifetime { apostrophe: p.span(), ident }), Some(tt2) => { bail!(TokenStream::from_iter(vec![tt.unwrap(), tt2]), "expected lifetime") } None => bail!(p, "expected lifetime"), } } // TODO: pass scope span if tt is None tt => bail!(tt, "expected lifetime"), } } fn parse_lifetime_def(input: &mut TokenIter) -> Result { let attrs = parse_attrs(input)?; let lifetime = parse_lifetime(input)?; let colon_token = input.parse_punct_opt(':'); let mut bounds = TokenStream::new(); if colon_token.is_some() { loop { if input.peek_t(&',') || input.peek_t(&'>') { break; } let value = parse_lifetime(input)?; value.to_tokens(&mut bounds); if !input.peek_t(&'+') { break; } let punct = input.parse_punct('+')?; punct.to_tokens(&mut bounds); } } Ok(LifetimeDef { attrs, lifetime, colon_token, bounds }) } fn parse_bound_lifetimes(input: &mut TokenIter) -> Result { Ok(BoundLifetimes { for_token: input.parse_kw("for")?, lt_token: input.parse_punct('<')?, lifetimes: { let mut lifetimes = vec![]; while !input.peek_t(&'>') { let lifetime = parse_lifetime_def(input)?; if input.peek_t(&'>') { lifetimes.push((lifetime, None)); break; } let punct = input.parse_punct(',')?; lifetimes.push((lifetime, Some(punct))); } lifetimes }, gt_token: input.parse_punct('>')?, }) } fn parse_type_param(input: &mut TokenIter) -> Result { let attrs = parse_attrs(input)?; let ident = input.parse_ident()?; let colon_token = input.parse_punct_opt(':'); let mut bounds = vec![]; if colon_token.is_some() { loop { if input.peek_t(&',') || input.peek_t(&'>') || input.peek_t(&'=') { break; } let is_maybe = input.peek_t(&'?') && !input.peek2_t(&"const"); let mut value = vec![]; append_tokens_until(input, &mut value, false, |next| match next { Some(TokenTree::Punct(p)) if p.as_char() == ',' || p.as_char() == '>' || p.as_char() == '=' || p.as_char() == '+' => { true } None => true, _ => false, })?; if !input.peek_t(&'+') { bounds.push((TypeParamBound::new(value, is_maybe), None)); break; } let punct = input.parse_punct('+')?; bounds.push((TypeParamBound::new(value, is_maybe), Some(punct))); } } let mut default = None; let eq_token = input.parse_punct_opt('='); if eq_token.is_some() { default = Some({ let mut ty = vec![]; append_tokens_until(input, &mut ty, false, |next| match next { Some(TokenTree::Punct(p)) if p.as_char() == '>' || p.as_char() == ',' => true, None => true, _ => false, })?; ty.into_iter().collect() }); } Ok(TypeParam { attrs, ident, colon_token, bounds, eq_token, default }) } fn const_argument(input: &mut TokenIter) -> Result { let tt = input.next(); match &tt { Some(TokenTree::Literal(_)) | Some(TokenTree::Ident(_)) => Ok(tt.unwrap()), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => Ok(tt.unwrap()), // TODO: pass scope span if tt is None _ => bail!(tt, "expected one of: literal, ident, `{`"), } } fn parse_const_param(input: &mut TokenIter) -> Result { let attrs = parse_attrs(input)?; let const_token = input.parse_kw("const")?; let ident = input.parse_ident()?; let colon_token = input.parse_punct(':')?; let mut ty = vec![]; append_tokens_until(input, &mut ty, false, |next| match next { Some(TokenTree::Punct(p)) if p.as_char() == '>' || p.as_char() == '=' && p.spacing() == Spacing::Alone || p.as_char() == ',' && p.spacing() == Spacing::Alone => { true } None => true, _ => false, })?; let mut default = None; let eq_token = if input.peek_t(&'=') { let eq_token = input.parse_punct('=')?; default = Some(std::iter::once(const_argument(input)?).collect()); Some(eq_token) } else { None }; Ok(ConstParam { attrs, const_token, ident, colon_token, ty: ty.into_iter().collect(), eq_token, default, }) } fn parse_where_clause(input: &mut TokenIter) -> Result { let where_token = input.parse_kw("where")?; let mut predicates = vec![]; loop { if input.is_empty() || input.peek_t(&Delimiter::Brace) || input.peek_t(&',') || input.peek_t(&';') || input.peek_t(&':') && !input.peek2_t(&':') || input.peek_t(&'=') { break; } let value = parse_where_predicate(input)?; if !input.peek_t(&',') { predicates.push((value, None)); break; } let punct = input.parse_punct(',')?; predicates.push((value, Some(punct))); } Ok(WhereClause { where_token, predicates }) } fn parse_where_predicate(input: &mut TokenIter) -> Result { if input.peek_lifetime() && input.peek3_t(&':') { Ok(WherePredicate::Lifetime(PredicateLifetime { lifetime: parse_lifetime(input)?, colon_token: input.parse_punct(':')?, bounds: { let mut bounds = vec![]; loop { if input.is_empty() || input.peek_t(&Delimiter::Brace) || input.peek_t(&',') || input.peek_t(&';') || input.peek_t(&':') || input.peek_t(&'=') { break; } let value = parse_lifetime(input)?; if !input.peek_t(&'+') { bounds.push((value, None)); break; } let punct = input.parse_punct('+')?; bounds.push((value, Some(punct))); } bounds }, })) } else { Ok(WherePredicate::Type(PredicateType { lifetimes: { if input.peek_t(&"for") { Some(parse_bound_lifetimes(input)?) } else { None } }, bounded_ty: { let mut ty = vec![]; append_tokens_until(input, &mut ty, false, |next| match next { Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => true, Some(TokenTree::Punct(p)) if p.as_char() == ',' || p.as_char() == '=' && p.spacing() == Spacing::Alone || p.as_char() == ':' && p.spacing() == Spacing::Alone => { true } _ => false, })?; ty.into_iter().collect() }, colon_token: input.parse_punct(':')?, bounds: { let mut bounds = vec![]; loop { if input.is_empty() || input.peek_t(&Delimiter::Brace) || input.peek_t(&',') || input.peek_t(&';') || input.peek_t(&':') && !input.peek2_t(&':') || input.peek_t(&'=') { break; } let is_maybe = input.peek_t(&'?') && !input.peek2_t(&"const"); let mut value = vec![]; append_tokens_until(input, &mut value, false, |next| match next { Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => true, Some(TokenTree::Punct(p)) if p.as_char() == ',' || p.as_char() == '>' || p.as_char() == '=' || p.as_char() == '+' => { true } None => true, _ => false, })?; if !input.peek_t(&'+') { bounds.push((TypeParamBound::new(value, is_maybe), None)); break; } let punct = input.parse_punct('+')?; bounds.push((TypeParamBound::new(value, is_maybe), Some(punct))); } bounds }, })) } } pub(crate) fn parse_visibility(input: &mut TokenIter) -> Result { if input.peek_t(&"pub") { let pub_token = input.parse_ident()?; match input.peek() { Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis {} => { let g = input.parse_group(Delimiter::Parenthesis)?; Ok(Visibility::Restricted(pub_token, g)) } _ => Ok(Visibility::Public(pub_token)), } } else { Ok(Visibility::Inherited) } } pub(crate) fn parse_inputs(input: TokenStream) -> Result> { let input = &mut TokenIter::new(input); let mut inputs = vec![]; loop { let mut pat = vec![]; append_tokens_until(input, &mut pat, false, |next| match next { Some(TokenTree::Punct(p)) if p.as_char() == ',' || p.as_char() == ':' => true, None => true, _ => false, })?; if !input.peek_t(&':') { if input.peek_t(&',') { inputs.push(FnArg::Receiver( pat.into_iter().collect(), Some(input.next().unwrap()), )); continue; } assert!(input.next().is_none()); inputs.push(FnArg::Receiver(pat.into_iter().collect(), None)); break; } let colon = input.parse_punct(':')?; let mut ty = vec![]; append_tokens_until(input, &mut ty, false, |next| match next { Some(TokenTree::Punct(p)) if p.as_char() == ',' => true, None => true, _ => false, })?; if input.peek_t(&',') { inputs.push(FnArg::Typed( pat.into_iter().collect(), colon, ty.into_iter().collect(), Some(input.next().unwrap()), )); continue; } assert!(input.next().is_none()); inputs.push(FnArg::Typed( pat.into_iter().collect(), colon, ty.into_iter().collect(), None, )); break; } Ok(inputs) } pub(crate) fn parse_impl(input: &mut TokenIter) -> Result { let attrs = parse_attrs(input)?; let vis: Visibility = parse_visibility(input)?; let defaultness = input.parse_kw_opt("default"); let unsafety = input.parse_kw_opt("unsafe"); let impl_token = input.parse_kw("impl")?; let has_generics = input.peek_t(&'<') && (input.peek2_t(&'>') || input.peek2_t(&'#') || input.peek2_ident().is_some() && (input.peek3_t(&':') || input.peek3_t(&',') || input.peek3_t(&'>') || input.peek3_t(&'=')) || input.peek2_lifetime() && (input.peek4_t(&':') || input.peek4_t(&',') || input.peek4_t(&'>') || input.peek4_t(&'=')) || input.peek2_t(&"const")); let mut generics: Generics = if has_generics { parse_generics(input)? } else { Generics::default() }; let const_token = input.parse_kw_opt("const"); let mut self_ty = vec![]; append_tokens_until(input, &mut self_ty, false, |next| match next { Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => true, Some(TokenTree::Ident(i)) if i.to_string() == "where" => true, _ => false, })?; if input.peek_t(&"where") { generics.where_clause = Some(parse_where_clause(input)?); } let g = input.parse_group(Delimiter::Brace)?; let brace_token = g.span(); let content = &mut TokenIter::new(g.stream()); let mut items = vec![]; while !content.is_empty() { items.push(parse_impl_item(content)?); } Ok(ItemImpl { attrs, vis, defaultness, unsafety, impl_token, generics, const_token, trait_: None, self_ty, brace_token, items, }) } fn parse_impl_item(input: &mut TokenIter) -> Result { let attrs = parse_attrs(input)?; let vis = parse_visibility(input)?; let defaultness = if input.peek_t(&"default") && !input.peek2_t(&'!') { Some(input.parse_kw("default")?) } else { None }; if peek_signature(input) { let sig = parse_signature(input)?; let body = input.parse_group(Delimiter::Brace)?; Ok(ImplItem::Method(ImplItemMethod { attrs, vis, defaultness, sig, body })) } else if input.peek_t(&"const") { let const_token = input.parse_kw("const")?; let ident = input.parse_ident()?; let colon_token = input.parse_punct(':')?; let mut ty = vec![]; append_tokens_until(input, &mut ty, false, |next| match next { Some(TokenTree::Punct(p)) if p.as_char() == '=' && p.spacing() == Spacing::Alone || p.as_char() == ';' && p.spacing() == Spacing::Alone => { true } _ => false, })?; let eq_token = input.parse_punct('=')?; let (expr, semi_token) = parse_until_punct(input, ';')?; Ok(ImplItem::Const(ImplItemConst { attrs, vis, defaultness, const_token, ident, colon_token, ty: ty.into_iter().collect(), eq_token, expr, semi_token, })) } else if input.peek_t(&"type") { let type_token = input.parse_kw("type")?; let ident = input.parse_ident()?; let mut generics = parse_generics(input)?; if input.peek_t(&"where") { generics.where_clause = Some(parse_where_clause(input)?); } let eq_token = input.parse_punct('=')?; let (ty, semi_token) = parse_until_punct(input, ';')?; Ok(ImplItem::Type(ImplItemType { attrs, vis, defaultness, type_token, ident, generics, eq_token, ty, semi_token, })) } else { bail!(input.next(), "expected one of: `default`, `fn`, `const`, `type`") } } fn peek_signature(input: &TokenIter) -> bool { let fork = &mut input.clone(); fork.parse_kw_opt("const"); fork.parse_kw_opt("async"); fork.parse_kw_opt("unsafe"); if fork.peek_t(&"extern") { let _extern_token = fork.parse_kw("extern"); fork.parse_literal_opt(); } fork.peek_t(&"fn") } fn parse_signature(input: &mut TokenIter) -> Result { let mut before_ident = vec![]; loop { let tt = input.tt()?; match &tt { TokenTree::Ident(i) if i.to_string() == "fn" => { before_ident.push(tt); break; } TokenTree::Group(g) if g.delimiter() == Delimiter::None => { let mut iter = g.stream().into_iter(); if let Some(TokenTree::Ident(i)) = iter.next() { if iter.next().is_none() && i.to_string() == "fn" { before_ident.push(tt); break; } } before_ident.push(tt); } _ => before_ident.push(tt), } } let ident = input.parse_ident()?; let mut generics = parse_generics(input)?; let inputs = input.parse_group(Delimiter::Parenthesis)?; let paren_token = inputs.span(); let inputs = parse_inputs(inputs.stream())?; let output = if input.peek_punct('-').map_or(false, |p| p.spacing() == Spacing::Joint) && input.peek2_t(&'>') { let arrow1 = input.tt()?; let arrow2 = input.tt()?; let mut tokens = vec![arrow1, arrow2]; append_tokens_until(input, &mut tokens, false, |next| match next { Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => true, Some(TokenTree::Ident(i)) if i.to_string() == "where" => true, None => true, _ => false, })?; Some(tokens.into_iter().collect()) } else { None }; if input.peek_t(&"where") { generics.where_clause = Some(parse_where_clause(input)?); } Ok(Signature { before_ident, ident, generics, paren_token, inputs, output }) } } pub(crate) mod printing { use proc_macro::{Delimiter, Group, Punct, Spacing, Span, TokenStream}; use super::{ Attribute, BoundLifetimes, ConstParam, FnArg, GenericParam, Generics, ImplGenerics, ImplItem, ImplItemConst, ImplItemMethod, ImplItemType, ItemImpl, ItemTrait, Lifetime, LifetimeDef, PredicateLifetime, PredicateType, Signature, TraitItem, TraitItemConst, TraitItemMethod, TraitItemType, TypeGenerics, TypeParam, TypeParamBound, Visibility, WhereClause, WherePredicate, }; use crate::to_tokens::ToTokens; pub(crate) fn punct(ch: char, span: Span) -> Punct { let mut p = Punct::new(ch, Spacing::Alone); p.set_span(span); p } fn tokens_or_default(p: &Option, ch: char, tokens: &mut TokenStream) { match p { Some(p) => p.to_tokens(tokens), None => punct(ch, Span::call_site()).to_tokens(tokens), } } impl ToTokens for Generics { fn to_tokens(&self, tokens: &mut TokenStream) { if self.params.is_empty() { return; } tokens_or_default(&self.lt_token, '<', tokens); // Print lifetimes before types and consts, regardless of their // order in self.params. // // TODO: ordering rules for const parameters vs type parameters have // not been settled yet. https://github.com/rust-lang/rust/issues/44580 let mut trailing_or_empty = true; for (param, p) in &self.params { if let GenericParam::Lifetime(_) = param { param.to_tokens(tokens); p.to_tokens(tokens); trailing_or_empty = p.is_some(); } } for (param, p) in &self.params { match param { GenericParam::Type(_) | GenericParam::Const(_) => { if !trailing_or_empty { punct(',', Span::call_site()).to_tokens(tokens); trailing_or_empty = true; } param.to_tokens(tokens); p.to_tokens(tokens); } GenericParam::Lifetime(_) => {} } } tokens_or_default(&self.gt_token, '>', tokens); } } impl ToTokens for GenericParam { fn to_tokens(&self, tokens: &mut TokenStream) { match self { GenericParam::Const(p) => p.to_tokens(tokens), GenericParam::Lifetime(l) => l.to_tokens(tokens), GenericParam::Type(t) => t.to_tokens(tokens), } } } impl ToTokens for BoundLifetimes { fn to_tokens(&self, tokens: &mut TokenStream) { self.for_token.to_tokens(tokens); self.lt_token.to_tokens(tokens); self.lifetimes.to_tokens(tokens); self.gt_token.to_tokens(tokens); } } impl ToTokens for Lifetime { fn to_tokens(&self, tokens: &mut TokenStream) { let mut apostrophe = Punct::new('\'', Spacing::Joint); apostrophe.set_span(self.apostrophe); apostrophe.to_tokens(tokens); self.ident.to_tokens(tokens); } } impl ToTokens for LifetimeDef { fn to_tokens(&self, tokens: &mut TokenStream) { self.attrs.to_tokens(tokens); self.lifetime.to_tokens(tokens); if !self.bounds.is_empty() { tokens_or_default(&self.colon_token, ':', tokens); self.bounds.to_tokens(tokens); } } } impl ToTokens for TypeParam { fn to_tokens(&self, tokens: &mut TokenStream) { self.attrs.to_tokens(tokens); self.ident.to_tokens(tokens); if !self.bounds.is_empty() { tokens_or_default(&self.colon_token, ':', tokens); for (bound, punct) in &self.bounds { bound.to_tokens(tokens); punct.to_tokens(tokens); } } if let Some(default) = &self.default { tokens_or_default(&self.eq_token, '=', tokens); default.to_tokens(tokens); } } } impl ToTokens for TypeParamBound { fn to_tokens(&self, tokens: &mut TokenStream) { self.tokens.to_tokens(tokens); } } impl ToTokens for ConstParam { fn to_tokens(&self, tokens: &mut TokenStream) { self.attrs.to_tokens(tokens); self.const_token.to_tokens(tokens); self.ident.to_tokens(tokens); self.colon_token.to_tokens(tokens); self.ty.to_tokens(tokens); if let Some(default) = &self.default { tokens_or_default(&self.eq_token, '=', tokens); default.to_tokens(tokens); } } } impl ToTokens for ImplGenerics<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { if self.0.params.is_empty() { return; } tokens_or_default(&self.0.lt_token, '<', tokens); // Print lifetimes before types and consts, regardless of their // order in self.params. // // TODO: ordering rules for const parameters vs type parameters have // not been settled yet. https://github.com/rust-lang/rust/issues/44580 let mut trailing_or_empty = true; for (param, p) in &self.0.params { if let GenericParam::Lifetime(_) = param { param.to_tokens(tokens); p.to_tokens(tokens); trailing_or_empty = p.is_some(); } } for (param, p) in &self.0.params { if let GenericParam::Lifetime(_) = param { continue; } if !trailing_or_empty { punct(',', Span::call_site()).to_tokens(tokens); trailing_or_empty = true; } match param { GenericParam::Lifetime(_) => unreachable!(), GenericParam::Type(param) => { // Leave off the type parameter defaults param.attrs.to_tokens(tokens); param.ident.to_tokens(tokens); if !param.bounds.is_empty() { tokens_or_default(¶m.colon_token, ':', tokens); param.bounds.to_tokens(tokens); } } GenericParam::Const(param) => { // Leave off the const parameter defaults param.attrs.to_tokens(tokens); param.const_token.to_tokens(tokens); param.ident.to_tokens(tokens); param.colon_token.to_tokens(tokens); param.ty.to_tokens(tokens); } } p.to_tokens(tokens); } tokens_or_default(&self.0.gt_token, '>', tokens); } } impl ToTokens for TypeGenerics<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { if self.0.params.is_empty() { return; } tokens_or_default(&self.0.lt_token, '<', tokens); // Print lifetimes before types and consts, regardless of their // order in self.params. // // TODO: ordering rules for const parameters vs type parameters have // not been settled yet. https://github.com/rust-lang/rust/issues/44580 let mut trailing_or_empty = true; for (param, p) in &self.0.params { if let GenericParam::Lifetime(def) = param { // Leave off the lifetime bounds and attributes def.lifetime.to_tokens(tokens); p.to_tokens(tokens); trailing_or_empty = p.is_some(); } } for (param, p) in &self.0.params { if let GenericParam::Lifetime(_) = param { continue; } if !trailing_or_empty { punct(',', Span::call_site()).to_tokens(tokens); trailing_or_empty = true; } match param { GenericParam::Lifetime(_) => unreachable!(), GenericParam::Type(param) => { // Leave off the type parameter defaults param.ident.to_tokens(tokens); } GenericParam::Const(param) => { // Leave off the const parameter defaults param.ident.to_tokens(tokens); } } p.to_tokens(tokens); } tokens_or_default(&self.0.gt_token, '>', tokens); } } impl ToTokens for WhereClause { fn to_tokens(&self, tokens: &mut TokenStream) { if !self.predicates.is_empty() { self.where_token.to_tokens(tokens); self.predicates.to_tokens(tokens); } } } impl ToTokens for WherePredicate { fn to_tokens(&self, tokens: &mut TokenStream) { match self { WherePredicate::Lifetime(l) => l.to_tokens(tokens), WherePredicate::Type(t) => t.to_tokens(tokens), } } } impl ToTokens for PredicateType { fn to_tokens(&self, tokens: &mut TokenStream) { self.lifetimes.to_tokens(tokens); self.bounded_ty.to_tokens(tokens); self.colon_token.to_tokens(tokens); self.bounds.to_tokens(tokens); } } impl ToTokens for PredicateLifetime { fn to_tokens(&self, tokens: &mut TokenStream) { self.lifetime.to_tokens(tokens); self.colon_token.to_tokens(tokens); self.bounds.to_tokens(tokens); } } impl ToTokens for Visibility { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Visibility::Public(i) => i.to_tokens(tokens), Visibility::Restricted(i, g) => { i.to_tokens(tokens); g.to_tokens(tokens); } Visibility::Inherited => {} } } } impl ToTokens for Attribute { fn to_tokens(&self, tokens: &mut TokenStream) { self.pound_token.to_tokens(tokens); self.tokens.to_tokens(tokens); } } fn group( span: Span, delimiter: Delimiter, tokens: &mut TokenStream, f: &dyn Fn(&mut TokenStream), ) { let mut inner = TokenStream::new(); f(&mut inner); let mut g = Group::new(delimiter, inner); g.set_span(span); g.to_tokens(tokens); } impl ToTokens for ItemTrait { fn to_tokens(&self, tokens: &mut TokenStream) { self.attrs.to_tokens(tokens); self.vis.to_tokens(tokens); self.unsafety.to_tokens(tokens); self.trait_token.to_tokens(tokens); self.ident.to_tokens(tokens); self.generics.to_tokens(tokens); self.generics.where_clause.to_tokens(tokens); group(self.brace_token, Delimiter::Brace, tokens, &|tokens| { self.items.to_tokens(tokens); }); } } impl ToTokens for ItemImpl { fn to_tokens(&self, tokens: &mut TokenStream) { self.attrs.to_tokens(tokens); self.defaultness.to_tokens(tokens); self.unsafety.to_tokens(tokens); self.impl_token.to_tokens(tokens); self.generics.impl_generics().to_tokens(tokens); self.const_token.to_tokens(tokens); if let Some((path, generics, for_)) = &self.trait_ { path.to_tokens(tokens); generics.to_tokens(tokens); for_.to_tokens(tokens); } self.self_ty.to_tokens(tokens); self.generics.where_clause.to_tokens(tokens); group(self.brace_token, Delimiter::Brace, tokens, &|tokens| { self.items.to_tokens(tokens); }); } } impl ToTokens for TraitItem { fn to_tokens(&self, tokens: &mut TokenStream) { match self { TraitItem::Const(i) => i.to_tokens(tokens), TraitItem::Method(i) => i.to_tokens(tokens), TraitItem::Type(i) => i.to_tokens(tokens), } } } impl ToTokens for TraitItemConst { fn to_tokens(&self, tokens: &mut TokenStream) { self.attrs.to_tokens(tokens); self.const_token.to_tokens(tokens); self.ident.to_tokens(tokens); self.colon_token.to_tokens(tokens); self.ty.to_tokens(tokens); self.semi_token.to_tokens(tokens); } } impl ToTokens for TraitItemMethod { fn to_tokens(&self, tokens: &mut TokenStream) { self.attrs.to_tokens(tokens); self.sig.to_tokens(tokens); self.semi_token.to_tokens(tokens); } } impl ToTokens for TraitItemType { fn to_tokens(&self, tokens: &mut TokenStream) { self.attrs.to_tokens(tokens); self.type_token.to_tokens(tokens); self.ident.to_tokens(tokens); self.generics.to_tokens(tokens); self.generics.where_clause.to_tokens(tokens); self.semi_token.to_tokens(tokens); } } impl ToTokens for ImplItem { fn to_tokens(&self, tokens: &mut TokenStream) { match self { ImplItem::Const(i) => i.to_tokens(tokens), ImplItem::Method(i) => i.to_tokens(tokens), ImplItem::Type(i) => i.to_tokens(tokens), } } } impl ToTokens for ImplItemConst { fn to_tokens(&self, tokens: &mut TokenStream) { self.attrs.to_tokens(tokens); self.vis.to_tokens(tokens); self.defaultness.to_tokens(tokens); self.const_token.to_tokens(tokens); self.ident.to_tokens(tokens); self.colon_token.to_tokens(tokens); self.ty.to_tokens(tokens); self.eq_token.to_tokens(tokens); self.expr.to_tokens(tokens); self.semi_token.to_tokens(tokens); } } impl ToTokens for ImplItemMethod { fn to_tokens(&self, tokens: &mut TokenStream) { self.attrs.to_tokens(tokens); self.vis.to_tokens(tokens); self.defaultness.to_tokens(tokens); self.sig.to_tokens(tokens); self.body.to_tokens(tokens); } } impl ToTokens for ImplItemType { fn to_tokens(&self, tokens: &mut TokenStream) { self.attrs.to_tokens(tokens); self.vis.to_tokens(tokens); self.defaultness.to_tokens(tokens); self.type_token.to_tokens(tokens); self.ident.to_tokens(tokens); self.generics.to_tokens(tokens); self.generics.where_clause.to_tokens(tokens); self.eq_token.to_tokens(tokens); self.ty.to_tokens(tokens); self.semi_token.to_tokens(tokens); } } impl ToTokens for Signature { fn to_tokens(&self, tokens: &mut TokenStream) { self.before_ident.to_tokens(tokens); self.ident.to_tokens(tokens); self.generics.to_tokens(tokens); group(self.paren_token, Delimiter::Parenthesis, tokens, &|tokens| { for arg in &self.inputs { arg.to_tokens(tokens); } }); self.output.to_tokens(tokens); self.generics.where_clause.to_tokens(tokens); } } impl ToTokens for FnArg { fn to_tokens(&self, tokens: &mut TokenStream) { match self { FnArg::Receiver(pat, p) => { pat.to_tokens(tokens); p.to_tokens(tokens); } FnArg::Typed(pat, colon, ty, p) => { pat.to_tokens(tokens); colon.to_tokens(tokens); ty.to_tokens(tokens); p.to_tokens(tokens); } } } } } easy-ext-1.0.2/src/error.rs000064400000000000000000000042571046102023000136610ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use std::iter::FromIterator; use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; use crate::to_tokens::ToTokens; macro_rules! format_err { ($span:expr, $msg:expr $(,)*) => { crate::error::Error::new(&$span, String::from($msg)) }; ($span:expr, $($tt:tt)*) => { format_err!($span, format!($($tt)*)) }; } macro_rules! bail { ($($tt:tt)*) => { return Err(format_err!($($tt)*)) }; } pub(crate) type Result = std::result::Result; #[derive(Debug)] pub(crate) struct Error { start_span: Span, end_span: Span, msg: String, } impl Error { pub(crate) fn new(tokens: &dyn ToTokens, msg: String) -> Self { let mut iter = tokens.to_token_stream().into_iter(); // `Span` on stable Rust has a limitation that only points to the first // token, not the whole tokens. We can work around this limitation by // using the first/last span of the tokens like `syn::Error::new_spanned` does. let start_span = iter.next().map_or_else(Span::call_site, |t| t.span()); let end_span = iter.last().map_or(start_span, |t| t.span()); Self { start_span, end_span, msg } } // Based on https://github.com/dtolnay/syn/blob/1.0.39/src/error.rs#L210-L237 pub(crate) fn into_compile_error(self) -> TokenStream { // compile_error!($msg) TokenStream::from_iter(vec![ TokenTree::Ident(Ident::new("compile_error", self.start_span)), TokenTree::Punct({ let mut punct = Punct::new('!', Spacing::Alone); punct.set_span(self.start_span); punct }), TokenTree::Group({ let mut group = Group::new(Delimiter::Brace, { TokenStream::from_iter(vec![TokenTree::Literal({ let mut string = Literal::string(&self.msg); string.set_span(self.end_span); string })]) }); group.set_span(self.end_span); group }), ]) } } easy-ext-1.0.2/src/iter.rs000064400000000000000000000211061046102023000134630ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use proc_macro::{ token_stream, Delimiter, Group, Ident, Literal, Punct, Spacing, TokenStream, TokenTree, }; use crate::error::Result; #[derive(Clone)] pub(crate) struct TokenIter { stack: Vec, peeked: Option, peeked2: Option, peeked3: Option, peeked4: Option, } impl TokenIter { pub(crate) fn new(tokens: TokenStream) -> Self { Self { stack: vec![tokens.into_iter()], peeked: None, peeked2: None, peeked3: None, peeked4: None, } } #[allow(clippy::wrong_self_convention)] pub(crate) fn is_empty(&mut self) -> bool { self.peek().is_none() } pub(crate) fn peek(&mut self) -> Option<&TokenTree> { self.peeked = self.next(); self.peeked.as_ref() } pub(crate) fn peek_t(&mut self, token: &dyn Token) -> bool { match self.peek() { Some(tt) => token.match_token(tt), None => false, } } pub(crate) fn peek2(&mut self) -> Option<&TokenTree> { let peeked = self.next(); let peeked2 = self.next(); self.peeked = peeked; self.peeked2 = peeked2; self.peeked2.as_ref() } pub(crate) fn peek2_t(&mut self, token: &dyn Token) -> bool { match self.peek2() { Some(tt) => token.match_token(tt), None => false, } } pub(crate) fn peek3(&mut self) -> Option<&TokenTree> { let peeked = self.next(); let peeked2 = self.next(); let peeked3 = self.next(); self.peeked = peeked; self.peeked2 = peeked2; self.peeked3 = peeked3; self.peeked3.as_ref() } pub(crate) fn peek3_t(&mut self, token: &dyn Token) -> bool { match self.peek3() { Some(tt) => token.match_token(tt), None => false, } } pub(crate) fn peek4(&mut self) -> Option<&TokenTree> { let peeked = self.next(); let peeked2 = self.next(); let peeked3 = self.next(); let peeked4 = self.next(); self.peeked = peeked; self.peeked2 = peeked2; self.peeked3 = peeked3; self.peeked4 = peeked4; self.peeked4.as_ref() } pub(crate) fn peek4_t(&mut self, token: &dyn Token) -> bool { match self.peek4() { Some(tt) => token.match_token(tt), None => false, } } pub(crate) fn peek_ident(&mut self) -> Option<&Ident> { match self.peek() { Some(TokenTree::Ident(i)) => Some(i), _ => None, } } pub(crate) fn peek2_ident(&mut self) -> Option<&Ident> { match self.peek2() { Some(TokenTree::Ident(i)) => Some(i), _ => None, } } pub(crate) fn peek3_ident(&mut self) -> Option<&Ident> { match self.peek3() { Some(TokenTree::Ident(i)) => Some(i), _ => None, } } pub(crate) fn parse_ident(&mut self) -> Result { match self.next() { Some(TokenTree::Ident(i)) => Ok(i), // TODO: pass scope span if tt is None tt => bail!(tt, "expected identifier"), } } pub(crate) fn parse_ident_opt(&mut self) -> Option { self.peek_ident()?; Some(self.parse_ident().unwrap()) } pub(crate) fn parse_kw(&mut self, kw: &str) -> Result { let tt = self.next(); match &tt { Some(TokenTree::Ident(i)) if i.to_string() == kw => { if let Some(TokenTree::Ident(i)) = tt { Ok(i) } else { unreachable!() } } // TODO: pass scope span if tt is None tt => bail!(tt, "expected `{}`", kw), } } pub(crate) fn parse_kw_opt(&mut self, kw: &str) -> Option { if self.peek_t(&kw) { Some(self.parse_ident().unwrap()) } else { None } } pub(crate) fn peek_punct(&mut self, ch: char) -> Option<&Punct> { match self.peek() { Some(TokenTree::Punct(p)) if p.as_char() == ch => Some(p), _ => None, } } pub(crate) fn peek2_punct(&mut self, ch: char) -> Option<&Punct> { match self.peek2() { Some(TokenTree::Punct(p)) if p.as_char() == ch => Some(p), _ => None, } } pub(crate) fn parse_punct(&mut self, ch: char) -> Result { let tt = self.next(); match &tt { Some(TokenTree::Punct(p)) if p.as_char() == ch => { if let Some(TokenTree::Punct(p)) = tt { Ok(p) } else { unreachable!() } } // TODO: pass scope span if tt is None tt => bail!(tt, "expected `{}`", ch), } } pub(crate) fn parse_punct_opt(&mut self, ch: char) -> Option { self.peek_punct(ch)?; Some(self.parse_punct(ch).unwrap()) } pub(crate) fn peek_lifetime(&mut self) -> bool { self.peek_punct('\'').map_or(false, |p| p.spacing() == Spacing::Joint) && self.peek2_ident().is_some() } pub(crate) fn peek2_lifetime(&mut self) -> bool { self.peek2_punct('\'').map_or(false, |p| p.spacing() == Spacing::Joint) && self.peek3_ident().is_some() } pub(crate) fn parse_group(&mut self, delimiter: Delimiter) -> Result { let tt = self.next(); match &tt { Some(TokenTree::Group(g)) if g.delimiter() == delimiter => { if let Some(TokenTree::Group(g)) = tt { Ok(g) } else { unreachable!() } } tt => { let d = match delimiter { Delimiter::Brace => "`{`", Delimiter::Bracket => "`[`", Delimiter::Parenthesis => "`(`", Delimiter::None => "none-delimited group", }; // TODO: pass scope span if tt is None bail!(tt, "expected {}", d) } } } pub(crate) fn peek_literal(&mut self) -> Option<&Literal> { match self.peek() { Some(TokenTree::Literal(l)) => Some(l), _ => None, } } pub(crate) fn parse_literal(&mut self) -> Result { match self.next() { Some(TokenTree::Literal(l)) => Ok(l), // TODO: pass scope span if tt is None tt => bail!(tt, "expected literal"), } } pub(crate) fn parse_literal_opt(&mut self) -> Option { self.peek_literal()?; Some(self.parse_literal().unwrap()) } pub(crate) fn tt(&mut self) -> Result { self.next().ok_or_else(|| { // TODO: pass scope span format_err!(TokenStream::new(), "unexpected end of input") }) } } // Based on https://github.com/dtolnay/proc-macro-hack/blob/0.5.19/src/iter.rs impl Iterator for TokenIter { type Item = TokenTree; fn next(&mut self) -> Option { if let Some(tt) = self.peeked.take() { return Some(tt); } if let Some(tt) = self.peeked2.take() { return Some(tt); } if let Some(tt) = self.peeked3.take() { return Some(tt); } if let Some(tt) = self.peeked4.take() { return Some(tt); } loop { let top = self.stack.last_mut()?; match top.next() { None => drop(self.stack.pop()), Some(TokenTree::Group(ref group)) if group.delimiter() == Delimiter::None => { self.stack.push(group.stream().into_iter()); } Some(tt) => return Some(tt), } } } } pub(crate) trait Token { fn match_token(&self, tt: &TokenTree) -> bool; } impl Token for char { fn match_token(&self, tt: &TokenTree) -> bool { match tt { TokenTree::Punct(p) => p.as_char() == *self, _ => false, } } } impl Token for &str { fn match_token(&self, tt: &TokenTree) -> bool { match tt { TokenTree::Ident(i) => i.to_string() == *self, _ => false, } } } impl Token for Delimiter { fn match_token(&self, tt: &TokenTree) -> bool { match tt { TokenTree::Group(g) => g.delimiter() == *self, _ => false, } } } easy-ext-1.0.2/src/lib.rs000064400000000000000000000452631046102023000133000ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT /*! A lightweight attribute macro for easily writing [extension trait pattern][rfc0445]. ```toml [dependencies] easy-ext = "1" ``` ## Examples ```rust use easy_ext::ext; #[ext(ResultExt)] pub impl Result { fn err_into(self) -> Result where E: Into, { self.map_err(Into::into) } } ``` Code like this will be generated: ```rust pub trait ResultExt { fn err_into(self) -> Result where E: Into; } impl ResultExt for Result { fn err_into(self) -> Result where E: Into, { self.map_err(Into::into) } } ``` You can elide the trait name. ```rust use easy_ext::ext; #[ext] impl Result { fn err_into(self) -> Result where E: Into, { self.map_err(Into::into) } } ``` Note that in this case, `#[ext]` assigns a random name, so you cannot import/export the generated trait. ### Visibility There are two ways to specify visibility. #### Impl-level visibility The first way is to specify visibility at the impl level. For example: ```rust use easy_ext::ext; // unnamed #[ext] pub impl str { fn foo(&self) {} } // named #[ext(StrExt)] pub impl str { fn bar(&self) {} } ``` #### Associated-item-level visibility Another way is to specify visibility at the associated item level. For example, if the method is `pub` then the trait will also be `pub`: ```rust use easy_ext::ext; #[ext(ResultExt)] // generate `pub trait ResultExt` impl Result { pub fn err_into(self) -> Result where E: Into, { self.map_err(Into::into) } } ``` This is useful when migrate from an inherent impl to an extension trait. Note that the visibility of all the associated items in the `impl` must be identical. Note that you cannot specify impl-level visibility and associated-item-level visibility at the same time. ### [Supertraits](https://doc.rust-lang.org/reference/items/traits.html#supertraits) If you want the extension trait to be a subtrait of another trait, add `Self: SubTrait` bound to the `where` clause. ```rust use easy_ext::ext; #[ext(Ext)] impl T where Self: Default, { fn method(&self) {} } ``` ### Supported items #### [Associated functions (methods)](https://doc.rust-lang.org/reference/items/associated-items.html#associated-functions-and-methods) ```rust use easy_ext::ext; #[ext] impl T { fn method(&self) {} } ``` #### [Associated constants](https://doc.rust-lang.org/reference/items/associated-items.html#associated-constants) ```rust use easy_ext::ext; #[ext] impl T { const MSG: &'static str = "Hello!"; } ``` #### [Associated types](https://doc.rust-lang.org/reference/items/associated-items.html#associated-types) ```rust use easy_ext::ext; #[ext] impl str { type Owned = String; fn method(&self) -> Self::Owned { self.to_owned() } } ``` [rfc0445]: https://github.com/rust-lang/rfcs/blob/HEAD/text/0445-extension-trait-conventions.md */ #![doc(test( no_crate_inject, attr( deny(warnings, rust_2018_idioms, single_use_lifetimes), allow(dead_code, unused_variables) ) ))] #![forbid(unsafe_code)] // older compilers require explicit `extern crate`. #[allow(unused_extern_crates)] extern crate proc_macro; #[macro_use] mod error; mod ast; mod iter; mod to_tokens; use std::{collections::hash_map::DefaultHasher, hash::Hasher, iter::FromIterator, mem}; use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; use crate::{ ast::{ parsing, printing::punct, Attribute, AttributeKind, FnArg, GenericParam, Generics, ImplItem, ItemImpl, ItemTrait, PredicateType, Signature, TraitItem, TraitItemConst, TraitItemMethod, TraitItemType, TypeParam, Visibility, WherePredicate, }, error::{Error, Result}, iter::TokenIter, to_tokens::ToTokens, }; /// A lightweight attribute macro for easily writing [extension trait pattern][rfc0445]. /// /// See crate level documentation for details. /// /// [rfc0445]: https://github.com/rust-lang/rfcs/blob/HEAD/text/0445-extension-trait-conventions.md #[proc_macro_attribute] pub fn ext(args: TokenStream, input: TokenStream) -> TokenStream { expand(args, input).unwrap_or_else(Error::into_compile_error) } fn expand(args: TokenStream, input: TokenStream) -> Result { let trait_name = match parse_args(args)? { None => Ident::new(&format!("__ExtTrait{}", hash(&input)), Span::call_site()), Some(trait_name) => trait_name, }; let mut item: ItemImpl = parsing::parse_impl(&mut TokenIter::new(input))?; let mut tokens = trait_from_impl(&mut item, trait_name)?.to_token_stream(); tokens.extend(item.to_token_stream()); Ok(tokens) } fn parse_args(input: TokenStream) -> Result> { let input = &mut TokenIter::new(input); let vis = ast::parsing::parse_visibility(input)?; if !vis.is_inherited() { bail!(vis, "use `{} impl` instead", vis); } let trait_name = input.parse_ident_opt(); if !input.is_empty() { let tt = input.next().unwrap(); bail!(tt, "unexpected token: `{}`", tt); } Ok(trait_name) } fn determine_trait_generics<'a>( generics: &mut Generics, self_ty: &'a [TokenTree], ) -> Option<&'a Ident> { if self_ty.len() != 1 { return None; } if let TokenTree::Ident(self_ty) = &self_ty[0] { let i = generics.params.iter().position(|(param, _)| { if let GenericParam::Type(param) = param { param.ident.to_string() == self_ty.to_string() } else { false } }); if let Some(i) = i { let mut params = mem::replace(&mut generics.params, vec![]); let (param, _) = params.remove(i); generics.params = params; if let GenericParam::Type(TypeParam { colon_token: Some(colon_token), bounds, .. }) = param { let bounds = bounds.into_iter().filter(|(b, _)| !b.is_maybe).collect::>(); if !bounds.is_empty() { let where_clause = generics.make_where_clause(); if let Some((_, p)) = where_clause.predicates.last_mut() { p.get_or_insert_with(|| punct(',', Span::call_site())); } where_clause.predicates.push(( WherePredicate::Type(PredicateType { lifetimes: None, bounded_ty: vec![TokenTree::Ident(Ident::new("Self", self_ty.span()))] .into_iter() .collect(), colon_token, bounds, }), None, )); } } return Some(self_ty); } } None } fn trait_from_impl(item: &mut ItemImpl, trait_name: Ident) -> Result { /// Replace `self_ty` with `Self`. struct ReplaceParam { self_ty: String, // Restrict the scope for removing `?Trait` bounds, because `?Trait` // bounds are only permitted at the point where a type parameter is // declared. remove_maybe: bool, } impl ReplaceParam { fn visit_token_stream(&self, tokens: &mut TokenStream) -> bool { let mut out: Vec = vec![]; let mut modified = false; let iter = tokens.clone().into_iter(); for tt in iter { match tt { TokenTree::Ident(ident) => { if ident.to_string() == self.self_ty { modified = true; let self_ = Ident::new("Self", ident.span()); out.push(self_.into()); } else { out.push(TokenTree::Ident(ident)); } } TokenTree::Group(group) => { let mut content = group.stream(); modified |= self.visit_token_stream(&mut content); let mut new = Group::new(group.delimiter(), content); new.set_span(group.span()); out.push(TokenTree::Group(new)); } other => out.push(other), } } if modified { *tokens = TokenStream::from_iter(out); } modified } // Everything below is simply traversing the syntax tree. fn visit_trait_item_mut(&mut self, node: &mut TraitItem) { match node { TraitItem::Const(node) => { self.visit_token_stream(&mut node.ty); } TraitItem::Method(node) => { self.visit_signature_mut(&mut node.sig); } TraitItem::Type(node) => { self.visit_generics_mut(&mut node.generics); } } } fn visit_signature_mut(&mut self, node: &mut Signature) { self.visit_generics_mut(&mut node.generics); for arg in &mut node.inputs { self.visit_fn_arg_mut(arg); } if let Some(ty) = &mut node.output { self.visit_token_stream(ty); } } fn visit_fn_arg_mut(&mut self, node: &mut FnArg) { match node { FnArg::Receiver(pat, _) => { self.visit_token_stream(pat); } FnArg::Typed(pat, _, ty, _) => { self.visit_token_stream(pat); self.visit_token_stream(ty); } } } fn visit_generics_mut(&mut self, generics: &mut Generics) { for (param, _) in &mut generics.params { match param { GenericParam::Type(param) => { for (bound, _) in &mut param.bounds { self.visit_token_stream(&mut bound.tokens); } } GenericParam::Const(_) | GenericParam::Lifetime(_) => {} } } if let Some(where_clause) = &mut generics.where_clause { let predicates = Vec::with_capacity(where_clause.predicates.len()); for (mut predicate, p) in mem::replace(&mut where_clause.predicates, predicates) { match &mut predicate { WherePredicate::Type(pred) => { if self.remove_maybe { let mut iter = pred.bounded_ty.clone().into_iter(); if let Some(TokenTree::Ident(i)) = iter.next() { if iter.next().is_none() && self.self_ty == i.to_string() { let bounds = mem::replace(&mut pred.bounds, vec![]) .into_iter() .filter(|(b, _)| !b.is_maybe) .collect::>(); if !bounds.is_empty() { self.visit_token_stream(&mut pred.bounded_ty); pred.bounds = bounds; for (bound, _) in &mut pred.bounds { self.visit_token_stream(&mut bound.tokens); } where_clause.predicates.push((predicate, p)); } continue; } } } self.visit_token_stream(&mut pred.bounded_ty); for (bound, _) in &mut pred.bounds { self.visit_token_stream(&mut bound.tokens); } } WherePredicate::Lifetime(_) => {} } where_clause.predicates.push((predicate, p)); } } } } let mut generics = item.generics.clone(); let mut visitor = determine_trait_generics(&mut generics, &item.self_ty) .map(|self_ty| ReplaceParam { self_ty: self_ty.to_string(), remove_maybe: false }); if let Some(visitor) = &mut visitor { visitor.remove_maybe = true; visitor.visit_generics_mut(&mut generics); visitor.remove_maybe = false; } let ty_generics = generics.ty_generics(); item.trait_ = Some(( trait_name.clone(), ty_generics.to_token_stream(), Ident::new("for", Span::call_site()), )); // impl-level visibility let impl_vis = if item.vis.is_inherited() { None } else { Some(item.vis.clone()) }; // assoc-item-level visibility let mut assoc_vis = None; let mut items = Vec::with_capacity(item.items.len()); item.items.iter_mut().try_for_each(|item| { trait_item_from_impl_item(item, &mut assoc_vis, &impl_vis).map(|mut item| { if let Some(visitor) = &mut visitor { visitor.visit_trait_item_mut(&mut item); } items.push(item); }) })?; let mut attrs = item.attrs.clone(); find_remove(&mut item.attrs, AttributeKind::Doc); // https://github.com/taiki-e/easy-ext/issues/20 attrs.push(Attribute::new(vec![ TokenTree::Ident(Ident::new("allow", Span::call_site())), TokenTree::Group(Group::new( Delimiter::Parenthesis, std::iter::once(TokenTree::Ident(Ident::new( "patterns_in_fns_without_body", Span::call_site(), ))) .collect(), )), ])); // mut self Ok(ItemTrait { attrs, // priority: impl-level visibility > assoc-item-level visibility > inherited visibility vis: impl_vis.unwrap_or_else(|| assoc_vis.unwrap_or(Visibility::Inherited)), unsafety: item.unsafety.clone(), trait_token: Ident::new("trait", item.impl_token.span()), ident: trait_name, generics, brace_token: item.brace_token, items, }) } fn trait_item_from_impl_item( impl_item: &mut ImplItem, prev_vis: &mut Option, impl_vis: &Option, ) -> Result { fn check_visibility( current: Visibility, prev: &mut Option, impl_vis: &Option, span: &dyn ToTokens, ) -> Result<()> { if impl_vis.is_some() { if current.is_inherited() { return Ok(()); } bail!(current, "all associated items must have inherited visibility"); } match prev { None => *prev = Some(current), Some(prev) if *prev == current => {} Some(prev) => { if prev.is_inherited() { bail!(current, "all associated items must have inherited visibility"); } bail!( if current.is_inherited() { span } else { ¤t }, "all associated items must have a visibility of `{}`", prev, ); } } Ok(()) } match impl_item { ImplItem::Const(impl_const) => { let vis = mem::replace(&mut impl_const.vis, Visibility::Inherited); check_visibility(vis, prev_vis, impl_vis, &impl_const.ident)?; let attrs = impl_const.attrs.clone(); find_remove(&mut impl_const.attrs, AttributeKind::Doc); // https://github.com/taiki-e/easy-ext/issues/20 Ok(TraitItem::Const(TraitItemConst { attrs, const_token: impl_const.const_token.clone(), ident: impl_const.ident.clone(), colon_token: impl_const.colon_token.clone(), ty: impl_const.ty.clone(), semi_token: impl_const.semi_token.clone(), })) } ImplItem::Type(impl_type) => { let vis = mem::replace(&mut impl_type.vis, Visibility::Inherited); check_visibility(vis, prev_vis, impl_vis, &impl_type.ident)?; let attrs = impl_type.attrs.clone(); find_remove(&mut impl_type.attrs, AttributeKind::Doc); // https://github.com/taiki-e/easy-ext/issues/20 Ok(TraitItem::Type(TraitItemType { attrs, type_token: impl_type.type_token.clone(), ident: impl_type.ident.clone(), generics: impl_type.generics.clone(), semi_token: impl_type.semi_token.clone(), })) } ImplItem::Method(impl_method) => { let vis = mem::replace(&mut impl_method.vis, Visibility::Inherited); check_visibility(vis, prev_vis, impl_vis, &impl_method.sig.ident)?; let mut attrs = impl_method.attrs.clone(); find_remove(&mut impl_method.attrs, AttributeKind::Doc); // https://github.com/taiki-e/easy-ext/issues/20 find_remove(&mut attrs, AttributeKind::Inline); // `#[inline]` is ignored on function prototypes Ok(TraitItem::Method(TraitItemMethod { attrs, sig: { let mut sig = impl_method.sig.clone(); for arg in &mut sig.inputs { if let FnArg::Typed(pat, ..) = arg { if pat.to_string() != "self" { *pat = std::iter::once(TokenTree::Ident(Ident::new( "_", pat.clone().into_iter().next().unwrap().span(), ))) .collect(); } } } sig }, semi_token: punct(';', impl_method.body.span()), })) } } } fn find_remove(attrs: &mut Vec, kind: AttributeKind) { while let Some(i) = attrs.iter().position(|attr| attr.kind == kind) { attrs.remove(i); } } /// Returns the hash value of the input AST. fn hash(input: &TokenStream) -> u64 { let mut hasher = DefaultHasher::new(); hasher.write(input.to_string().as_bytes()); hasher.finish() } easy-ext-1.0.2/src/to_tokens.rs000064400000000000000000000034211046102023000145250ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use std::iter; use proc_macro::{Group, Ident, Punct, TokenStream, TokenTree}; pub(crate) trait ToTokens { fn to_tokens(&self, tokens: &mut TokenStream); fn to_token_stream(&self) -> TokenStream { let mut tokens = TokenStream::new(); self.to_tokens(&mut tokens); tokens } } impl ToTokens for Ident { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(iter::once(TokenTree::Ident(self.clone()))); } } impl ToTokens for Punct { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(iter::once(TokenTree::Punct(self.clone()))); } } impl ToTokens for Group { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(iter::once(TokenTree::Group(self.clone()))); } } impl ToTokens for TokenTree { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(iter::once(self.clone())); } } impl ToTokens for TokenStream { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(self.clone()); } } impl ToTokens for Option { fn to_tokens(&self, tokens: &mut TokenStream) { if let Some(t) = self { T::to_tokens(t, tokens); } } } impl ToTokens for &T { fn to_tokens(&self, tokens: &mut TokenStream) { T::to_tokens(self, tokens); } } impl ToTokens for [T] { fn to_tokens(&self, tokens: &mut TokenStream) { for t in self { T::to_tokens(t, tokens); } } } impl ToTokens for [(T, Option)] { fn to_tokens(&self, tokens: &mut TokenStream) { for (t, p) in self { T::to_tokens(t, tokens); p.to_tokens(tokens); } } } easy-ext-1.0.2/tests/compiletest.rs000064400000000000000000000003701046102023000154230ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT #![cfg(not(miri))] #[rustversion::attr(not(nightly), ignore)] #[test] fn ui() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/**/*.rs"); t.pass("tests/run-pass/**/*.rs"); } easy-ext-1.0.2/tests/run-pass/associated_type_bounds.rs000064400000000000000000000150671046102023000214060ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT #![allow(dead_code)] // TODO: move to tests/test.rs once Rust 1.79 become stable. // https://github.com/rust-lang/rust/blob/1.70.0/tests/ui/associated-type-bounds/fn-where.rs pub mod fn_where { use easy_ext::ext; use crate::fn_aux::*; #[ext(E1)] impl T { fn where_bound(beta: B) -> usize where B: Beta, { desugared_bound(beta) } fn where_bound_region(beta: B) -> usize where B: Beta, { desugared_bound_region(beta) } fn where_bound_multi(beta: B) -> usize where B: Copy + Beta, { desugared_bound_multi(beta) } fn where_bound_region_specific<'a, B>(gamma: &'a B::Gamma) -> usize where B: Beta>, { desugared_bound_region_specific::(gamma) } fn where_bound_region_forall(beta: B) -> usize where B: Beta Epsilon<'a>>, { desugared_bound_region_forall(beta) } fn where_bound_region_forall2(beta: B) -> usize where B: Beta Epsilon<'a, Zeta: Eta>>, { desugared_bound_region_forall2(beta) } fn where_constraint_region_forall(beta: B) -> usize where for<'a> &'a B: Beta, { desugared_constraint_region_forall(beta) } fn where_bound_nested(beta: B) -> usize where B: Beta>, { desugared_bound_nested(beta) } } } // https://github.com/rust-lang/rust/blob/1.70.0/tests/ui/associated-type-bounds/auxiliary/fn-aux.rs mod fn_aux { // Traits: pub trait Alpha { fn alpha(self) -> usize; } pub trait Beta { type Gamma; fn gamma(self) -> Self::Gamma; } pub trait Delta { fn delta(self) -> usize; } pub trait Epsilon<'a> { type Zeta; fn zeta(&'a self) -> Self::Zeta; fn epsilon(&'a self) -> usize; } pub trait Eta { fn eta(self) -> usize; } // Assertions: pub fn assert_alpha(x: T) -> usize { x.alpha() } pub fn assert_static(_: T) -> usize { 24 } pub fn assert_delta(x: T) -> usize { x.delta() } pub fn assert_epsilon_specific<'a, T: 'a + Epsilon<'a>>(x: &'a T) -> usize { x.epsilon() } pub fn assert_epsilon_forall Epsilon<'a>>() {} pub fn assert_forall_epsilon_zeta_satisfies_eta(x: T) -> usize where T: for<'a> Epsilon<'a>, for<'a> >::Zeta: Eta, { x.epsilon() + x.zeta().eta() } // Implementations and types: #[derive(Copy, Clone)] pub struct BetaType; #[derive(Copy, Clone)] pub struct GammaType; #[derive(Copy, Clone)] pub struct ZetaType; impl Beta for BetaType { type Gamma = GammaType; fn gamma(self) -> Self::Gamma { GammaType } } impl<'a> Beta for &'a BetaType { type Gamma = GammaType; fn gamma(self) -> Self::Gamma { GammaType } } impl Beta for GammaType { type Gamma = Self; fn gamma(self) -> Self::Gamma { self } } impl Alpha for GammaType { fn alpha(self) -> usize { 42 } } impl Delta for GammaType { fn delta(self) -> usize { 1337 } } impl<'a> Epsilon<'a> for GammaType { type Zeta = ZetaType; fn zeta(&'a self) -> Self::Zeta { ZetaType } fn epsilon(&'a self) -> usize { 7331 } } impl Eta for ZetaType { fn eta(self) -> usize { 7 } } // Desugared forms to check against: pub fn desugared_bound(beta: B) -> usize where B: Beta, B::Gamma: Alpha, { let gamma: B::Gamma = beta.gamma(); assert_alpha::(gamma) } pub fn desugared_bound_region(beta: B) -> usize where B: Beta, B::Gamma: 'static, { assert_static::(beta.gamma()) } pub fn desugared_bound_multi(beta: B) -> usize where B: Copy + Beta, B::Gamma: Alpha + 'static + Delta, { assert_alpha::(beta.gamma()) + assert_static::(beta.gamma()) + assert_delta::(beta.gamma()) } pub fn desugared_bound_region_specific<'a, B>(gamma: &'a B::Gamma) -> usize where B: Beta, B::Gamma: 'a + Epsilon<'a>, { assert_epsilon_specific::(gamma) } pub fn desugared_bound_region_forall(beta: B) -> usize where B: Beta, B::Gamma: Copy + for<'a> Epsilon<'a>, { assert_epsilon_forall::(); let g1: B::Gamma = beta.gamma(); let g2: B::Gamma = g1; assert_epsilon_specific::(&g1) + assert_epsilon_specific::(&g2) } pub fn desugared_bound_region_forall2(beta: B) -> usize where B: Beta, B::Gamma: Copy + for<'a> Epsilon<'a>, for<'a> >::Zeta: Eta, { let gamma = beta.gamma(); assert_forall_epsilon_zeta_satisfies_eta::(gamma) } pub fn desugared_constraint_region_forall(beta: B) -> usize where for<'a> &'a B: Beta, for<'a> <&'a B as Beta>::Gamma: Alpha, { let g1 = beta.gamma(); let g2 = beta.gamma(); assert_alpha(g1) + assert_alpha(g2) } pub fn desugared_bound_nested(beta: B) -> usize where B: Beta, B::Gamma: Copy + Alpha + Beta, ::Gamma: Delta, { let go = beta.gamma(); let gi = go.gamma(); go.alpha() + gi.delta() } pub fn desugared() { let beta = BetaType; let gamma = beta.gamma(); assert_eq!(42, desugared_bound(beta)); assert_eq!(24, desugared_bound_region(beta)); assert_eq!(42 + 24 + 1337, desugared_bound_multi(beta)); assert_eq!(7331, desugared_bound_region_specific::(&gamma)); assert_eq!(7331 * 2, desugared_bound_region_forall(beta)); assert_eq!(42 + 1337, desugared_bound_nested(beta)); } } fn main() {} easy-ext-1.0.2/tests/run-pass/const_trait_impl.rs000064400000000000000000000010451046102023000202150ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT #![feature(const_trait_impl)] /* TODO: update for https://github.com/rust-lang/rust/pull/100982 // https://github.com/rust-lang/rust/issues/67792 // https://github.com/rust-lang/rust/blob/1.63.0/src/test/ui/rfc-2632-const-trait-impl/call-const-trait-method-pass.rs use easy_ext::ext; #[ext(Ext)] impl const i32 { fn plus(self, rhs: Self) -> Self { self + rhs } } pub const fn add_i32(a: i32, b: i32) -> i32 { a.plus(b) } const ADD_I32: i32 = 1i32.plus(2i32); */ fn main() {} easy-ext-1.0.2/tests/run-pass/impl_trait_in_assoc_type.rs000064400000000000000000000004261046102023000217300ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT #![feature(impl_trait_in_assoc_type)] use easy_ext::ext; #[ext(E1)] impl I where I: Iterator, { type Assoc = impl Iterator; fn assoc(self) -> Self::Assoc { self } } fn main() {} easy-ext-1.0.2/tests/run-pass/min_specialization.rs000064400000000000000000000026101046102023000205230ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT #![feature(min_specialization)] // See also run-pass/specialization.rs. // https://github.com/rust-lang/rust/blob/1.70.0/tests/ui/specialization/min_specialization/implcit-well-formed-bounds.rs pub mod implicit_well_formed_bounds { use easy_ext::ext; struct OrdOnly(T); #[ext(SpecTrait)] impl T { default fn f() {} } impl SpecTrait<()> for OrdOnly { fn f() {} } impl SpecTrait> for () { fn f() {} } impl SpecTrait<(OrdOnly, OrdOnly)> for &[OrdOnly] { fn f() {} } } // https://github.com/rust-lang/rust/blob/1.70.0/tests/ui/specialization/min_specialization/spec-iter.rs pub mod spec_iter { use easy_ext::ext; #[ext(SpecFromIter)] impl<'a, T: 'a, I: Iterator> I { default fn f(&self) {} } // See also spec_iter module in ui/min_specialization.rs. impl<'a, T> SpecFromIter<'a, T> for std::slice::Iter<'a, T> { fn f(&self) {} } } // https://github.com/rust-lang/rust/blob/1.70.0/tests/ui/specialization/min_specialization/spec-reference.rs pub mod spec_reference { use easy_ext::ext; #[ext(MySpecTrait)] impl T { default fn f() {} } impl<'a, T: ?Sized> MySpecTrait for &'a T { fn f() {} } } fn main() {} easy-ext-1.0.2/tests/run-pass/specialization.rs000064400000000000000000000023061046102023000176620ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT #![allow(incomplete_features)] #![feature(specialization)] // See also run-pass/min_specialization.rs. pub mod default_impl { // I don't feel `default impl` is the good feature to combine with ext trait, but test anyway. // https://github.com/rust-lang/rust/blob/1.70.0/tests/ui/specialization/defaultimpl/auxiliary/go_trait.rs pub mod go_trait { use easy_ext::ext; pub trait Go { fn go(&self, arg: isize); } pub fn go(this: &G, arg: isize) { this.go(arg) } pub fn go_mut(this: &mut G, arg: isize) { this.go_mut(arg) } pub fn go_once(this: G, arg: isize) { this.go_once(arg) } #[ext(GoMut)] pub default impl G where G: Go, { fn go_mut(&mut self, arg: isize) { go(&*self, arg) } } #[ext(GoOnce)] pub default impl G where G: GoMut, { fn go_once(mut self, arg: isize) { go_mut(&mut self, arg) } } } } fn main() {} easy-ext-1.0.2/tests/test.rs000064400000000000000000000345711046102023000140640ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT #![allow( dead_code, unreachable_pub, clippy::items_after_statements, clippy::missing_safety_doc, clippy::no_effect_underscore_binding, clippy::undocumented_unsafe_blocks )] use std::{pin::Pin, rc::Rc}; use async_trait::async_trait; use easy_ext::ext; #[test] fn simple() { #[ext] impl str { fn foo(&self, pat: &str) -> String { self.replace(pat, "_") } } assert_eq!("--".foo("-"), "__"); } #[test] fn params() { #[ext] impl Result { fn err_into(self) -> Result where E: Into, { self.map_err(Into::into) } } let err: Result<(), _> = Err(1_u32); assert_eq!(err.err_into::().unwrap_err(), 1_u64); } #[test] fn lifetime() { #[ext(OptionExt)] impl<'a, T> &'a mut Option { fn into_ref(self) -> Option<&'a T> { self.as_ref() } } let _: Option<&u8> = Some(1).into_ref(); } mod bar { use easy_ext::ext; // assoc-item-level visibility + named #[ext(E1)] impl str { pub const FOO1: &'static str = "_"; pub fn foo1(&self, pat: &str) -> String { self.replace(pat, Self::FOO1) } } // assoc-item-level visibility + unnamed #[ext] impl str { pub const FOO2: &'static str = "_"; pub fn foo2(&self, pat: &str) -> String { self.replace(pat, Self::FOO2) } } // impl-level visibility + named #[ext(E2)] pub impl str { const FOO3: &'static str = "_"; fn foo3(&self, pat: &str) -> String { self.replace(pat, Self::FOO3) } } // impl-level visibility + unnamed #[ext] pub impl str { const FOO4: &'static str = "_"; fn foo4(&self, pat: &str) -> String { self.replace(pat, Self::FOO4) } } pub(super) mod baz { use easy_ext::ext; #[ext(E4)] impl str { pub(super) fn bar(&self, pat: &str) -> String { self.replace(pat, "_") } } #[ext(E5)] impl str { pub fn baz(&self, pat: &str) -> String { self.replace(pat, "_") } pub fn baz2(&self, pat: &str) -> String { self.replace(pat, "-") } } #[ext(E6)] pub(super) impl str { fn bar2(&self, pat: &str) -> String { self.replace(pat, "_") } } #[ext(E7)] pub(crate) impl str { fn baz3(&self, pat: &str) -> String { self.replace(pat, "_") } fn baz4(&self, pat: &str) -> String { self.replace(pat, "-") } } } } #[test] fn visibility() { use self::bar::{ baz::{E5, E7}, E1, E2, }; assert_eq!("..".foo1("."), "__"); assert_eq!("..".foo3("."), "__"); assert_eq!("..".baz("."), "__"); assert_eq!("..".baz2("."), "--"); assert_eq!("..".baz3("."), "__"); assert_eq!("..".baz4("."), "--"); } #[test] fn generics() { #[ext(IterExt)] impl I { fn _next(self) -> Option { self.into_iter().next() } } assert_eq!(vec![1, 2, 3]._next(), Some(1_u8)); } #[test] fn trait_generics() { #[derive(Debug, PartialEq, Eq)] struct A {} impl Iterator for A { type Item = (); fn next(&mut self) -> Option { None } } #[ext(ConstInit)] impl A { const INIT1: Self = Self {}; const INIT2: A = A {}; } #[ext(Ext1)] impl I { const CONST1: Self = Self::INIT1; const CONST2: I = I::INIT1; type Item2 = Self::Item; type Item3 = I::Item; fn method1(mut self) -> Option { self.next() } fn method2(mut self) -> Option { self.next() } fn method3(mut self) -> Option { self.next() } fn method4(mut self) -> Option<::Item3> { self.next() } } fn a(mut x: T) { let y = T::CONST1; let _ = T::CONST2; assert_eq!(x, y); assert!(x.next().is_none()); } assert_eq!(A {}.method1(), None); assert_eq!(A {}.method2(), None); a(A::INIT1); a(A::INIT2); #[ext(Ext2)] impl I { const CONST3: I = { fn a() {} I::INIT1 }; type Item4 = I::Item; fn method5(self, _: I::Item) -> (Option, ::Item4) { fn a() {} unimplemented!() } } } #[test] fn type_parameter_defaults() { #[ext(Ext)] impl () {} impl Ext for u8 {} // The code above is equivalent to the code below. trait Trait {} impl Trait for () {} impl Trait for u8 {} } // See also ui/maybe.rs #[test] fn maybe() { #[ext] impl T { fn f(&self) {} } #[ext] impl T where T: ?Sized, { fn f(&self) {} } #[ext] impl T { fn f(&self) {} } #[ext] impl T where T: Send + ?Sized + Sync, { fn f(&self) {} } #[ext] impl T where T: Iterator, T: ?Sized, T: Default, { fn f(&self) {} } } #[test] fn inline() { #[ext] impl str { #[inline] fn auto(&self) {} #[inline(always)] fn always(&self) {} #[inline(never)] fn never(&self) {} } } #[test] fn assoc_ty() { #[ext(StrExt)] impl str { type Assoc = String; fn owned(&self) -> Self::Assoc { self.to_owned() } } let s: ::Assoc = "?".owned(); assert_eq!(s, "?"); #[ext(TryIterator)] impl>, T, E> I { type Ok = T; type Error = E; fn try_next(&mut self) -> Result, Self::Error> { self.next().transpose() } } let mut iter = vec![Ok(1), Err(1)].into_iter(); assert_eq!(iter.try_next(), Ok(Some(1))); assert_eq!(iter.try_next(), Err(1)); assert_eq!(iter.try_next(), Ok(None)); } #[allow(clippy::let_underscore_future)] #[test] fn syntax() { #[ext(E1)] unsafe impl str { fn normal(&self) {} unsafe fn unsafety(&self) {} extern "C" fn abi1() {} extern "C" fn abi2() {} unsafe extern "C" fn unsafe_abi1() {} unsafe extern "C" fn unsafe_abi2() {} } "a".normal(); unsafe { "?".unsafety() }; str::abi1(); unsafe { str::unsafe_abi1() }; struct S {} unsafe impl E1 for S { fn normal(&self) {} unsafe fn unsafety(&self) {} extern "C" fn abi1() {} extern "C" fn abi2() {} unsafe extern "C" fn unsafe_abi1() {} unsafe extern "C" fn unsafe_abi2() {} } #[ext(E2)] #[async_trait] impl str { async fn asyncness(&self) {} async unsafe fn unsafe_asyncness(&self) {} } let _ = async { "a".asyncness().await; unsafe { "b".unsafe_asyncness().await } }; #[async_trait] impl E2 for S { async fn asyncness(&self) {} async unsafe fn unsafe_asyncness(&self) {} } } // test for angle bracket #[test] fn angle_bracket() { #[ext] impl fn() -> () { const FUNC: fn() -> fn() -> fn() -> () = Self::func; type Func = fn() -> fn() -> (); fn func() -> fn() -> fn() -> () { || || {} } } #[ext(E1)] impl T where Self::Assoc3: Sized, T::Assoc3: Sized, Self: E2, T: E2, { const ASSOC1: ::Assoc1 = ::assoc1; type Assoc1 = fn() -> ::Assoc2; type Assoc2 = (); fn assoc1() -> ::Assoc2 where ::Assoc1: Sized, ::Assoc1: Sized, Self::Assoc1: Sized, T::Assoc3: Sized, Self: E2, T: E2, { } } struct A {} #[ext(E2)] impl A { const ASSOC1: ::Assoc3 = ::assoc2; type Assoc3 = fn() -> ::Assoc4; type Assoc4 = (); fn assoc2() -> ::Assoc4 where ::Assoc3: Sized, Self::Assoc3: Sized, { } } #[ext] impl fn() -> T, E> Result where E: FnOnce() -> Result, &'static dyn Fn() -> T: Fn() -> T + 'static, fn() -> fn() -> T: Fn() -> fn() -> T, { fn where_clause fn() -> T, F>(self, _f: F) -> Self where F: FnOnce() -> Result, &'static dyn Fn() -> T: Fn() -> T + 'static, fn() -> fn() -> T: Fn() -> fn() -> T, { unimplemented!() } } } #[test] fn min_const_generics() { struct S1([T; CAP]); #[ext(E1)] impl S1 { const CAPACITY: usize = CAP; fn f() -> S1 { unimplemented!() } } struct S2; impl E1<(), { CAP }> for S2<{ CAP }> { const CAPACITY: usize = CAP; fn f() -> S1<(), { C }> { S1([(); C]) } } let _: [(); 2] = >::f::<2>().0; struct S3(T); #[ext(E2)] impl str { fn method1(&self) -> S1 ()>, 1> { S1([Some(|| {})]) } #[allow(unused_braces)] fn method2(&self) -> S1 ()>, { 1 }> { S1([Some(|| {})]) } fn method3(&self) -> S3 (), 'a'> { S3(|| {}) } #[allow(unused_braces)] fn method4(&self) -> S3 (), { 'a' }> { S3(|| {}) } } } #[test] fn const_generics_defaults() { // https://github.com/rust-lang/rust/tree/1.70.0/tests/ui/const-generics/defaults #[ext(Ext)] impl () {} impl Ext for u8 {} // The code above is equivalent to the code below. trait Trait {} impl Trait for () {} impl Trait for u8 {} // https://github.com/rust-lang/rust/blob/1.70.0/tests/ui/const-generics/defaults/const-param-as-default-value.rs #[ext(Ext2)] impl () {} } #[test] fn generic_associated_types() { // https://github.com/rust-lang/rust/blob/1.70.0/tests/ui/generic-associated-types/collections.rs trait CollectionFamily { type Member: Collection; } struct VecFamily; impl CollectionFamily for VecFamily { type Member = Vec; } #[ext(Collection)] impl Vec { // TODO: handle where clause in GAT: https://github.com/rust-lang/rust/pull/90076 // type Iter<'iter> = std::slice::Iter<'iter, T> // where // T: 'iter, // Self: 'iter; type Family = VecFamily; type Sibling = <>::Family as CollectionFamily>::Member; fn empty() -> Self { vec![] } fn add(&mut self, value: T) { self.push(value); } // TODO: handle where clause in GAT: https://github.com/rust-lang/rust/pull/90076 // fn iterate<'iter>(&'iter self) -> Self::Iter<'iter> { // self.iter() // } } } #[test] fn macros() { macro_rules! m { ( $impl:ident $path:path [$($generics:tt)*] $where:ident {$( [$vis:vis, $($fn_sig:ident)*] )*} ) => { $( #[ext] $impl Result { $vis $($fn_sig)* (self) -> Result where E: Into, { unimplemented!() } } )* }; } m!(impl Result [T,E] where { [, fn a] [pub, fn b] [pub, unsafe fn c] [pub(crate), fn d] }); } // https://github.com/taiki-e/easy-ext/issues/36 #[test] fn where_clause() { pub trait Trait {} #[rustfmt::skip] #[ext] pub impl Vec where Self: Trait> { } } #[allow(clippy::needless_pub_self)] // This is intentional pub mod visibility { use easy_ext::ext; pub struct Pub; #[ext] impl str { pub const ASSOC: u8 = 1; pub type Assoc = u8; pub fn assoc() {} } pub struct PubCrate; #[ext] impl PubCrate { pub(crate) const ASSOC: u8 = 1; pub(crate) type Assoc = u8; pub(crate) fn assoc() {} } pub struct PubSelf; #[ext] impl PubSelf { pub(self) const ASSOC: u8 = 1; pub(self) type Assoc = u8; pub(self) fn assoc() {} } pub mod m { use easy_ext::ext; pub struct PubSuper; #[ext] impl PubSuper { pub(super) const ASSOC: u8 = 1; pub(super) type Assoc = u8; pub(super) fn assoc() {} } pub struct PubIn; #[ext] impl PubIn { pub(in super::m) const ASSOC: u8 = 1; pub(in super::m) type Assoc = u8; pub(in super::m) fn assoc() {} } } } #[test] fn arg_pat() { #[ext] impl str { fn f((x, y): (u8, u8)) { let _x = x; let _y = y; } } } #[test] fn arbitrary_self_types() { #[ext] #[allow(clippy::needless_arbitrary_self_type)] impl String { fn recv(self: Self) {} fn recv_ref(self: &Self) {} fn recv_mut(self: &mut Self) {} fn recv_rc(self: Rc) {} fn recv_rc_ref(self: &Rc) {} fn recv_rc_mut(self: &mut Rc) {} fn recv_pin_box(self: Pin>) {} } String::default().recv(); String::default().recv_ref(); String::default().recv_mut(); Rc::new(String::default()).recv_rc(); Rc::new(String::default()).recv_rc_ref(); Rc::new(String::default()).recv_rc_mut(); Box::pin(String::default()).recv_pin_box(); } easy-ext-1.0.2/tests/ui/invalid.rs000064400000000000000000000027121046102023000151400ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT mod basic { use easy_ext::ext; #[ext(NoValueConst)] impl str { const ASSOC: u8; //~ ERROR expected `=` } #[ext(NoValueTy)] impl str { type Assoc; //~ ERROR expected `=` } #[ext(NoValueFn)] impl str { fn assoc(); //~ ERROR expected `{` } #[ext(Macro)] impl str { mac!(); //~ ERROR expected one of: `default`, `fn`, `const`, `type` } #[rustfmt::skip] #[ext(ExtraArg,)] //~ ERROR unexpected token impl str {} #[ext(pub OldVisSyntax1)] //~ ERROR use `pub impl` instead impl str {} #[ext(pub(crate) OldVisSyntax2)] //~ ERROR use `pub(crate) impl` instead impl str {} } mod visibility { use easy_ext::ext; #[ext(AssocLevel1)] impl str { pub const ASSOC1: u8 = 1; const ASSOC2: u8 = 2; //~ ERROR all associated items must have a visibility of `pub` } #[ext(AssocLevel2)] impl str { fn assoc1(&self) {} pub fn assoc2(&self) {} //~ ERROR all associated items must have inherited visibility } #[ext(AssocLevel3)] impl str { pub(crate) type Assoc1 = (); pub type Assoc2 = (); //~ ERROR all associated items must have a visibility of `pub(crate)` } #[ext(ImplLevel1)] pub impl str { fn assoc1(&self) {} pub fn assoc2(&self) {} //~ ERROR all associated items must have inherited visibility } } fn main() {} easy-ext-1.0.2/tests/ui/invalid.stderr000064400000000000000000000036311046102023000160200ustar 00000000000000error: expected `=` --> tests/ui/invalid.rs:8:24 | 8 | const ASSOC: u8; //~ ERROR expected `=` | ^ error: expected `=` --> tests/ui/invalid.rs:12:19 | 12 | type Assoc; //~ ERROR expected `=` | ^ error: expected `{` --> tests/ui/invalid.rs:16:19 | 16 | fn assoc(); //~ ERROR expected `{` | ^ error: expected one of: `default`, `fn`, `const`, `type` --> tests/ui/invalid.rs:21:9 | 21 | mac!(); //~ ERROR expected one of: `default`, `fn`, `const`, `type` | ^^^ error: unexpected token: `,` --> tests/ui/invalid.rs:25:19 | 25 | #[ext(ExtraArg,)] //~ ERROR unexpected token | ^ error: use `pub impl` instead --> tests/ui/invalid.rs:28:11 | 28 | #[ext(pub OldVisSyntax1)] //~ ERROR use `pub impl` instead | ^^^ error: use `pub(crate) impl` instead --> tests/ui/invalid.rs:31:11 | 31 | #[ext(pub(crate) OldVisSyntax2)] //~ ERROR use `pub(crate) impl` instead | ^^^^^^^^^^ error: all associated items must have a visibility of `pub` --> tests/ui/invalid.rs:41:15 | 41 | const ASSOC2: u8 = 2; //~ ERROR all associated items must have a visibility of `pub` | ^^^^^^ error: all associated items must have inherited visibility --> tests/ui/invalid.rs:48:9 | 48 | pub fn assoc2(&self) {} //~ ERROR all associated items must have inherited visibility | ^^^ error: all associated items must have a visibility of `pub(crate)` --> tests/ui/invalid.rs:54:9 | 54 | pub type Assoc2 = (); //~ ERROR all associated items must have a visibility of `pub(crate)` | ^^^ error: all associated items must have inherited visibility --> tests/ui/invalid.rs:61:9 | 61 | pub fn assoc2(&self) {} //~ ERROR all associated items must have inherited visibility | ^^^ easy-ext-1.0.2/tests/ui/maybe.rs000064400000000000000000000023461046102023000146120ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT // See also `maybe` test in test.rs use easy_ext::ext; #[ext(E1)] impl T // Ok { fn f(&self) {} } #[ext(E2)] impl T where T: ?Sized, // Ok { fn f(&self) {} } #[ext(E3)] impl T where Self: ?Sized, { //~^^ ERROR `?Trait` bounds are only permitted at the point where a type parameter is declared fn f(&self) {} } // The following is a case where #[ext] is not used. The behavior should match in both cases. trait T1 where Self: ?Sized, { //~^^ ERROR `?Trait` bounds are only permitted at the point where a type parameter is declared fn f(&self); } trait T2 { fn f(&self); } impl T2 for T where Self: ?Sized, { //~^^ ERROR `?Trait` bounds are only permitted at the point where a type parameter is declared fn f(&self) {} } trait T3 { fn f(&self); } impl T3 for T // Ok { fn f(&self) {} } trait T4 { fn f(&self); } impl T4 for T where T: ?Sized, // Ok { fn f(&self) {} } trait T5 { fn f(&self); } impl T5 for T { fn f(&self) where T: ?Sized, { //~^^ ERROR `?Trait` bounds are only permitted at the point where a type parameter is declared } } fn main() {} easy-ext-1.0.2/tests/ui/maybe.stderr000064400000000000000000000012551046102023000154670ustar 00000000000000error: `?Trait` bounds are only permitted at the point where a type parameter is declared --> tests/ui/maybe.rs:34:11 | 34 | Self: ?Sized, | ^^^^^^ error: `?Trait` bounds are only permitted at the point where a type parameter is declared --> tests/ui/maybe.rs:45:11 | 45 | Self: ?Sized, | ^^^^^^ error: `?Trait` bounds are only permitted at the point where a type parameter is declared --> tests/ui/maybe.rs:75:12 | 75 | T: ?Sized, | ^^^^^^ error: `?Trait` bounds are only permitted at the point where a type parameter is declared --> tests/ui/maybe.rs:24:11 | 24 | Self: ?Sized, | ^^^^^^ easy-ext-1.0.2/tests/ui/visibility.rs000064400000000000000000000016331046102023000157020ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT mod foo { use easy_ext::ext; #[ext(StrExt1)] impl str { fn method1(&self, pat: &str) -> String { self.replace(pat, "_") } } #[ext(StrExt2)] pub(self) impl str { fn method2(&self, pat: &str) -> String { self.replace(pat, "_") } } pub mod bar { use easy_ext::ext; #[ext(StrExt3)] pub(super) impl str { fn method3(&self, pat: &str) -> String { self.replace(pat, "_") } } } #[allow(unused_imports)] use bar::StrExt3; } fn main() { #[rustfmt::skip] use foo::StrExt1; //~ ERROR trait `StrExt1` is private [E0603] #[rustfmt::skip] use foo::StrExt2; //~ ERROR trait `StrExt2` is private [E0603] #[rustfmt::skip] use foo::bar::StrExt3; //~ ERROR trait `StrExt2` is private [E0603] } easy-ext-1.0.2/tests/ui/visibility.stderr000064400000000000000000000017641046102023000165660ustar 00000000000000error[E0603]: trait `StrExt1` is private --> tests/ui/visibility.rs:37:14 | 37 | use foo::StrExt1; //~ ERROR trait `StrExt1` is private [E0603] | ^^^^^^^ private trait | note: the trait `StrExt1` is defined here --> tests/ui/visibility.rs:7:5 | 7 | impl str { | ^^^^^^^^ error[E0603]: trait `StrExt2` is private --> tests/ui/visibility.rs:39:14 | 39 | use foo::StrExt2; //~ ERROR trait `StrExt2` is private [E0603] | ^^^^^^^ private trait | note: the trait `StrExt2` is defined here --> tests/ui/visibility.rs:14:5 | 14 | pub(self) impl str { | ^^^^^^^^^^^^^^^^^^ error[E0603]: trait `StrExt3` is private --> tests/ui/visibility.rs:41:19 | 41 | use foo::bar::StrExt3; //~ ERROR trait `StrExt2` is private [E0603] | ^^^^^^^ private trait | note: the trait `StrExt3` is defined here --> tests/ui/visibility.rs:24:9 | 24 | pub(super) impl str { | ^^^^^^^^^^^^^^^^^^^