diff options
Diffstat (limited to 'third_party/rust/extend')
29 files changed, 1226 insertions, 0 deletions
diff --git a/third_party/rust/extend/.cargo-checksum.json b/third_party/rust/extend/.cargo-checksum.json new file mode 100644 index 0000000000..3ef7391275 --- /dev/null +++ b/third_party/rust/extend/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"910cd2057d356b18cd4f21791bc23956734a83b0f58701ec0d0d03405f5c21df","Cargo.toml":"928b8b97607b3589b85a6d4d00fb1cf64b78bee6354c303d8f49fbfa2ca5bdae","LICENSE":"db0efd0bb80126f32214f478434536d07d86a30658fb209d8b543b7261492a2b","README.md":"0872b2b15ec22732a09b121a31cdd8f8cfc80566f671566e4a7fb6b52a742a2e","deny.toml":"60448e9313f1883a7adf30713fcca789c454e77228105926024ce3abe9293100","rustfmt.toml":"9bbb759b3914c49c8060bcdeeb7786f4aec083d30bcfe236bbd48d8388d06103","src/lib.rs":"85bd23c064c78dcd141cb2fecca6fff2ee7e1dfb4cf19b74ab97ef30d51f6f03","tests/compile_fail/double_vis.rs":"524e8bc996f5a45de3d15f0c741d4f3414004e9c636c52de0f011785cc535109","tests/compile_fail/double_vis.stderr":"3748e67e6f41bd5624946ad7cdc4e2b30a9d0b5e76bc27c19ecc9d62da733978","tests/compile_fail/supertraits_are_actually_included.rs":"c03980316c4e71ceb7e80ab7011f00b6f1f8f89906670e9823267a4118f72853","tests/compile_fail/supertraits_are_actually_included.stderr":"835e79bb1bceb48e9db5f0a410c57af51c856e17917ef72f65e3ae2af79d97ec","tests/compile_pass/associated_constants.rs":"f3e56405650ddd31c33e932846553e88fe7181585e0f09a7414d50d521b0f103","tests/compile_pass/async_trait.rs":"9138801d67dbe20ed87fdb3f48bb968f0bfa11fe2d27414d5ea5ea7a6e44bb35","tests/compile_pass/changing_extension_trait_name.rs":"046e7a66151ce05c3515b4bea36be72f83b98f3bdb06fa6f065ad6fc373e19f2","tests/compile_pass/complex_trait_name.rs":"5a8c3588c26df07973739fbb85619d0de480cc8c992611dfc2dbcb9ab3c6e2b6","tests/compile_pass/double_ext_on_same_type.rs":"0e4d16fe9059f0325e4ad1337d53738876633b0533f9a2d4e9714ddada96eb4d","tests/compile_pass/extension_on_complex_types.rs":"88bdf979e1f399d7f9824165730b33884bc9c46347d246f8cc12e0e2039642a1","tests/compile_pass/generics.rs":"367f089e8010d1c98ccc975b03acb6cf03810793e1198105f75c006eafd52bb8","tests/compile_pass/hello_world.rs":"1961cfa634143974f0d64b26f817ac3148375faa5cf5864d63f0127d3af8099e","tests/compile_pass/issue_2.rs":"a14d5e2179b74ff71c5357e8a4f1a9ac228a711196edd40c12719565c8dfaa21","tests/compile_pass/more_than_one_extension.rs":"1d7486e4e9e4095d7e7f42aac878d836f021c49fbe26c31cb13fe6a0415a8558","tests/compile_pass/multiple_config.rs":"dc3b06e4fafbb7236b5bc49f9a5e9693241c07118da8886dd0752b895ff27425","tests/compile_pass/multiple_generic_params.rs":"2ac052ea7b818b6d45ee8070cf6ec18953419473f711375cafbe7283244b5743","tests/compile_pass/pub_impl.rs":"8dfcba21fbbc45efcf85b66fca2d187022569e72d4b10b57047eddda67a1725b","tests/compile_pass/ref_and_ref_mut.rs":"d1086a23809cbd8f87487677073238c3657a8a98fafc63a688eb9d622bf2eb11","tests/compile_pass/sized.rs":"baceaaabcf368b3c72e6d27fea72aadde11d97d9d153c35df5e2d89fb3da4e3e","tests/compile_pass/super_trait.rs":"07448e1fe2b9018125ccaabf7db1e2a244788519bb42117f8e437e48358eb2f0","tests/compile_pass/visibility_config.rs":"64846014a63327661fb250cdb726cce532ed82b1cea5cb83308526bcba22b4be"},"package":"5c5216e387a76eebaaf11f6d871ec8a4aae0b25f05456ee21f228e024b1b3610"}
\ No newline at end of file diff --git a/third_party/rust/extend/CHANGELOG.md b/third_party/rust/extend/CHANGELOG.md new file mode 100644 index 0000000000..f17149a2eb --- /dev/null +++ b/third_party/rust/extend/CHANGELOG.md @@ -0,0 +1,79 @@ +# Change Log + +All user visible changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/), as described +for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md) + +## Unreleased + +None. + +### Breaking changes + +None. + +## 1.1.2 - 2021-09-02 + +- Fix using `pub impl` with `#[async_trait]`. + +## 1.1.1 - 2021-06-12 + +- Fix name collision for extensions on `&T` and `&mut T`. The generated traits + now get different names. + +## 1.1.0 - 2021-06-12 + +- Support setting visibility of the generated trait directly on the `impl` + block. For example: `pub impl i32 { ... }`. +- Add `#[ext_sized]` for adding `Sized` supertrait. + +## 1.0.1 - 2021-02-14 + +- Update maintenance status. + +## 1.0.0 - 2021-01-30 + +- Support extensions on bare functions types (things like `fn(i32) -> bool`). +- Support extensions on trait objects (things like `dyn Send + Sync`). + +## 0.3.0 - 2020-08-31 + +- Add async-trait compatibility. + +### Breaking changes + +- Other attributes put on the `impl` would previously only be included on the generated trait. They're now included on both the trait and the implementation. + +## 0.2.1 - 2020-08-29 + +- Fix documentation link in Cargo.toml. +- Use more correct repository URL in Cargo.toml. + +## 0.2.0 - 2020-08-29 + +- Handle unnamed extensions on the same generic type with different type parameters. For example `Option<i32>` and `Option<String>`. Previously we would generate the same name of both hidden traits which wouldn't compile. +- Support associated constants in extension impls. + +### Breaking changes + +- Generated traits are no longer sealed and the `sealed` argument previously supported by `#[ext]` has been removed. Making the traits sealed lead to lots of complexity that we didn't think brought much value. + +## 0.1.1 - 2020-02-22 + +- Add support for specifying supertraits of the generated trait [#4](https://github.com/davidpdrsn/extend/pull/4). + +## 0.1.0 + +- Support adding extensions to the ["never type"](https://doc.rust-lang.org/std/primitive.never.html). + +### Breaking changes + +- Simplify names of traits generates for complex types. + +## 0.0.2 + +- Move "trybuild" to dev-dependency. + +## 0.0.1 + +Initial release. diff --git a/third_party/rust/extend/Cargo.toml b/third_party/rust/extend/Cargo.toml new file mode 100644 index 0000000000..dcc2ebae15 --- /dev/null +++ b/third_party/rust/extend/Cargo.toml @@ -0,0 +1,48 @@ +# 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 believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "extend" +version = "1.1.2" +authors = ["David Pedersen <david.pdrsn@gmail.com>"] +description = "Create extensions for types you don't own with extension traits but without the boilerplate." +homepage = "https://github.com/davidpdrsn/extend" +documentation = "https://docs.rs/extend" +readme = "README.md" +keywords = ["extension", "trait"] +categories = ["rust-patterns"] +license = "MIT" +repository = "https://github.com/davidpdrsn/extend.git" + +[lib] +path = "src/lib.rs" +proc-macro = true +[dependencies.proc-macro-error] +version = "1" + +[dependencies.proc-macro2] +version = "1" + +[dependencies.quote] +version = "1" + +[dependencies.syn] +version = "1" +features = ["full", "extra-traits", "visit"] +[dev-dependencies.async-trait] +version = "0.1.40" + +[dev-dependencies.trybuild] +version = "1.0.17" +[badges.maintenance] +status = "passively-maintained" diff --git a/third_party/rust/extend/LICENSE b/third_party/rust/extend/LICENSE new file mode 100644 index 0000000000..0cf5bc7301 --- /dev/null +++ b/third_party/rust/extend/LICENSE @@ -0,0 +1,19 @@ +MIT License Copyright (c) 2020 David Pedersen + +Permission 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 (including the next +paragraph) 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. diff --git a/third_party/rust/extend/README.md b/third_party/rust/extend/README.md new file mode 100644 index 0000000000..ffa19e951a --- /dev/null +++ b/third_party/rust/extend/README.md @@ -0,0 +1,32 @@ +# extend + +[![Crates.io](https://img.shields.io/crates/v/extend.svg)](https://crates.io/crates/extend) +[![Docs](https://docs.rs/extend/badge.svg)](https://docs.rs/extend) +[![dependency status](https://deps.rs/repo/github/davidpdrsn/extend/status.svg)](https://deps.rs/repo/github/davidpdrsn/extend) +[![Build status](https://github.com/davidpdrsn/extend/workflows/CI/badge.svg)](https://github.com/davidpdrsn/extend/actions) +![maintenance-status](https://img.shields.io/badge/maintenance-passively--maintained-yellowgreen.svg) + +Create extensions for types you don't own with [extension traits] but without the boilerplate. + +Example: + +```rust +use extend::ext; + +#[ext] +impl<T: Ord> Vec<T> { + fn sorted(mut self) -> Self { + self.sort(); + self + } +} + +fn main() { + assert_eq!( + vec![1, 2, 3], + vec![2, 3, 1].sorted(), + ); +} +``` + +[extension traits]: https://dev.to/matsimitsu/extending-existing-functionality-in-rust-with-traits-in-rust-3622 diff --git a/third_party/rust/extend/deny.toml b/third_party/rust/extend/deny.toml new file mode 100644 index 0000000000..57816c7a0d --- /dev/null +++ b/third_party/rust/extend/deny.toml @@ -0,0 +1,47 @@ +targets = [] + +[advisories] +db-path = "~/.cargo/advisory-db" +db-urls = ["https://github.com/rustsec/advisory-db"] +vulnerability = "deny" +unmaintained = "warn" +yanked = "warn" +notice = "warn" +ignore = [] + +[licenses] +unlicensed = "deny" +allow = [ + "MIT", + "Apache-2.0", +] +deny = [] +copyleft = "warn" +allow-osi-fsf-free = "neither" +default = "deny" +confidence-threshold = 0.8 +exceptions = [] + +[licenses.private] +ignore = false +registries = [] + +[bans] +multiple-versions = "deny" +wildcards = "allow" +highlight = "all" +allow = [] +deny = [] +skip = [] +skip-tree = [] + +[sources] +unknown-registry = "warn" +unknown-git = "warn" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +allow-git = [] + +[sources.allow-org] +github = [] +gitlab = [] +bitbucket = [] diff --git a/third_party/rust/extend/rustfmt.toml b/third_party/rust/extend/rustfmt.toml new file mode 100644 index 0000000000..7d2cf549dc --- /dev/null +++ b/third_party/rust/extend/rustfmt.toml @@ -0,0 +1 @@ +merge_imports = true diff --git a/third_party/rust/extend/src/lib.rs b/third_party/rust/extend/src/lib.rs new file mode 100644 index 0000000000..9b8188a4a0 --- /dev/null +++ b/third_party/rust/extend/src/lib.rs @@ -0,0 +1,620 @@ +//! Create extensions for types you don't own with [extension traits] but without the boilerplate. +//! +//! Example: +//! +//! ```rust +//! use extend::ext; +//! +//! #[ext] +//! impl<T: Ord> Vec<T> { +//! fn sorted(mut self) -> Self { +//! self.sort(); +//! self +//! } +//! } +//! +//! fn main() { +//! assert_eq!( +//! vec![1, 2, 3], +//! vec![2, 3, 1].sorted(), +//! ); +//! } +//! ``` +//! +//! # How does it work? +//! +//! Under the hood it generates a trait with methods in your `impl` and implements those for the +//! type you specify. The code shown above expands roughly to: +//! +//! ```rust +//! trait VecExt<T: Ord> { +//! fn sorted(self) -> Self; +//! } +//! +//! impl<T: Ord> VecExt<T> for Vec<T> { +//! fn sorted(mut self) -> Self { +//! self.sort(); +//! self +//! } +//! } +//! ``` +//! +//! # Supported items +//! +//! Extensions can contain methods or associated constants: +//! +//! ```rust +//! use extend::ext; +//! +//! #[ext] +//! impl String { +//! const CONSTANT: &'static str = "FOO"; +//! +//! fn method() { +//! // ... +//! # todo!() +//! } +//! } +//! ``` +//! +//! # Configuration +//! +//! You can configure: +//! +//! - The visibility of the trait. Use `pub impl ...` to generate `pub trait ...`. The default +//! visibility is private. +//! - The name of the generated extension trait. Example: `#[ext(name = MyExt)]`. By default we +//! generate a name based on what you extend. +//! - Which supertraits the generated extension trait should have. Default is no supertraits. +//! Example: `#[ext(supertraits = Default + Clone)]`. +//! +//! More examples: +//! +//! ```rust +//! use extend::ext; +//! +//! #[ext(name = SortedVecExt)] +//! impl<T: Ord> Vec<T> { +//! fn sorted(mut self) -> Self { +//! self.sort(); +//! self +//! } +//! } +//! +//! #[ext] +//! pub(crate) impl i32 { +//! fn double(self) -> i32 { +//! self * 2 +//! } +//! } +//! +//! #[ext(name = ResultSafeUnwrapExt)] +//! pub impl<T> Result<T, std::convert::Infallible> { +//! fn safe_unwrap(self) -> T { +//! match self { +//! Ok(t) => t, +//! Err(_) => unreachable!(), +//! } +//! } +//! } +//! +//! #[ext(supertraits = Default + Clone)] +//! impl String { +//! fn my_length(self) -> usize { +//! self.len() +//! } +//! } +//! ``` +//! +//! For backwards compatibility you can also declare the visibility as the first argument to `#[ext]`: +//! +//! ``` +//! use extend::ext; +//! +//! #[ext(pub)] +//! impl i32 { +//! fn double(self) -> i32 { +//! self * 2 +//! } +//! } +//! ``` +//! +//! # async-trait compatibility +//! +//! Async extensions are supported via [async-trait](https://crates.io/crates/async-trait). +//! +//! Be aware that you need to add `#[async_trait]` _below_ `#[ext]`. Otherwise the `ext` macro +//! cannot see the `#[async_trait]` attribute and pass it along in the generated code. +//! +//! Example: +//! +//! ``` +//! use extend::ext; +//! use async_trait::async_trait; +//! +//! #[ext] +//! #[async_trait] +//! impl String { +//! async fn read_file() -> String { +//! // ... +//! # todo!() +//! } +//! } +//! ``` +//! +//! # Other attributes +//! +//! Other attributes provided _below_ `#[ext]` will be passed along to both the generated trait and +//! the implementation. See [async-trait compatibility](#async-trait-compatibility) above for an +//! example. +//! +//! [extension traits]: https://dev.to/matsimitsu/extending-existing-functionality-in-rust-with-traits-in-rust-3622 + +#![doc(html_root_url = "https://docs.rs/extend/1.1.2")] +#![allow(clippy::let_and_return)] +#![deny( + unused_variables, + mutable_borrow_reservation_conflict, + dead_code, + unused_must_use, + unused_imports +)] + +use proc_macro2::TokenStream; +use proc_macro_error::*; +use quote::{format_ident, quote, ToTokens}; +use syn::{ + parse::{self, Parse, ParseStream}, + parse_macro_input, parse_quote, + punctuated::Punctuated, + spanned::Spanned, + token::{Add, Semi}, + Ident, ImplItem, ItemImpl, Token, TraitItemConst, TraitItemMethod, Type, TypeArray, TypeBareFn, + TypeGroup, TypeNever, TypeParamBound, TypeParen, TypePath, TypePtr, TypeReference, TypeSlice, + TypeTraitObject, TypeTuple, Visibility, +}; + +#[derive(Debug)] +struct Input { + item_impl: ItemImpl, + vis: Option<Visibility>, +} + +impl Parse for Input { + fn parse(input: ParseStream) -> syn::Result<Self> { + let mut attributes = Vec::new(); + if input.peek(syn::Token![#]) { + attributes.extend(syn::Attribute::parse_outer(input)?); + } + + let vis = input + .parse::<Visibility>() + .ok() + .filter(|vis| vis != &Visibility::Inherited); + + let mut item_impl = input.parse::<ItemImpl>()?; + item_impl.attrs.extend(attributes); + + Ok(Self { item_impl, vis }) + } +} + +/// See crate docs for more info. +#[proc_macro_attribute] +#[proc_macro_error] +#[allow(clippy::unneeded_field_pattern)] +pub fn ext( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let item = parse_macro_input!(item as Input); + let config = parse_macro_input!(attr as Config); + go(item, config) +} + +/// Like [`ext`](macro@crate::ext) but always add `Sized` as a supertrait. +/// +/// This is provided as a convenience for generating extension traits that require `Self: Sized` +/// such as: +/// +/// ``` +/// use extend::ext_sized; +/// +/// #[ext_sized] +/// impl i32 { +/// fn requires_sized(self) -> Option<Self> { +/// Some(self) +/// } +/// } +/// ``` +#[proc_macro_attribute] +#[proc_macro_error] +#[allow(clippy::unneeded_field_pattern)] +pub fn ext_sized( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let item = parse_macro_input!(item as Input); + let mut config: Config = parse_macro_input!(attr as Config); + + config.supertraits = if let Some(supertraits) = config.supertraits.take() { + Some(parse_quote!(#supertraits + Sized)) + } else { + Some(parse_quote!(Sized)) + }; + + go(item, config) +} + +fn go(item: Input, mut config: Config) -> proc_macro::TokenStream { + if let Some(vis) = item.vis { + if config.visibility != Visibility::Inherited { + abort!( + config.visibility.span(), + "Cannot set visibility on `#[ext]` and `impl` block" + ); + } + + config.visibility = vis; + } + + let ItemImpl { + attrs, + unsafety, + generics, + trait_, + self_ty, + items, + // What is defaultness? + defaultness: _, + impl_token: _, + brace_token: _, + } = item.item_impl; + + if let Some((_, path, _)) = trait_ { + abort!(path.span(), "Trait impls cannot be used for #[ext]"); + } + + let self_ty = parse_self_ty(&self_ty); + + let ext_trait_name = config + .ext_trait_name + .unwrap_or_else(|| ext_trait_name(&self_ty)); + + let MethodsAndConsts { + trait_methods, + trait_consts, + } = extract_allowed_items(&items); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let visibility = &config.visibility; + + let mut all_supertraits = Vec::<TypeParamBound>::new(); + + if let Some(supertraits_from_config) = config.supertraits { + all_supertraits.extend(supertraits_from_config); + } + + let supertraits_quoted = if all_supertraits.is_empty() { + quote! {} + } else { + let supertraits_quoted = punctuated_from_iter::<_, _, Add>(all_supertraits); + quote! { : #supertraits_quoted } + }; + + let code = (quote! { + #[allow(non_camel_case_types)] + #(#attrs)* + #visibility + #unsafety + trait #ext_trait_name #impl_generics #supertraits_quoted #where_clause { + #( + #trait_consts + )* + + #( + #[allow( + patterns_in_fns_without_body, + clippy::inline_fn_without_body, + unused_attributes + )] + #trait_methods + )* + } + + #(#attrs)* + impl #impl_generics #ext_trait_name #ty_generics for #self_ty #where_clause { + #(#items)* + } + }) + .into(); + + code +} + +#[derive(Debug, Clone)] +enum ExtType<'a> { + Array(&'a TypeArray), + Group(&'a TypeGroup), + Never(&'a TypeNever), + Paren(&'a TypeParen), + Path(&'a TypePath), + Ptr(&'a TypePtr), + Reference(&'a TypeReference), + Slice(&'a TypeSlice), + Tuple(&'a TypeTuple), + BareFn(&'a TypeBareFn), + TraitObject(&'a TypeTraitObject), +} + +#[allow(clippy::wildcard_in_or_patterns)] +fn parse_self_ty(self_ty: &Type) -> ExtType { + match self_ty { + Type::Array(inner) => ExtType::Array(inner), + Type::Group(inner) => ExtType::Group(inner), + Type::Never(inner) => ExtType::Never(inner), + Type::Paren(inner) => ExtType::Paren(inner), + Type::Path(inner) => ExtType::Path(inner), + Type::Ptr(inner) => ExtType::Ptr(inner), + Type::Reference(inner) => ExtType::Reference(inner), + Type::Slice(inner) => ExtType::Slice(inner), + Type::Tuple(inner) => ExtType::Tuple(inner), + Type::BareFn(inner) => ExtType::BareFn(inner), + Type::TraitObject(inner) => ExtType::TraitObject(inner), + + Type::ImplTrait(_) | Type::Infer(_) | Type::Macro(_) | Type::Verbatim(_) | _ => abort!( + self_ty.span(), + "#[ext] is not supported for this kind of type" + ), + } +} + +impl<'a> From<&'a Type> for ExtType<'a> { + fn from(inner: &'a Type) -> ExtType<'a> { + parse_self_ty(inner) + } +} + +impl<'a> ToTokens for ExtType<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + ExtType::Array(inner) => inner.to_tokens(tokens), + ExtType::Group(inner) => inner.to_tokens(tokens), + ExtType::Never(inner) => inner.to_tokens(tokens), + ExtType::Paren(inner) => inner.to_tokens(tokens), + ExtType::Path(inner) => inner.to_tokens(tokens), + ExtType::Ptr(inner) => inner.to_tokens(tokens), + ExtType::Reference(inner) => inner.to_tokens(tokens), + ExtType::Slice(inner) => inner.to_tokens(tokens), + ExtType::Tuple(inner) => inner.to_tokens(tokens), + ExtType::BareFn(inner) => inner.to_tokens(tokens), + ExtType::TraitObject(inner) => inner.to_tokens(tokens), + } + } +} + +fn ext_trait_name(self_ty: &ExtType) -> Ident { + fn inner_self_ty(self_ty: &ExtType) -> Ident { + match self_ty { + ExtType::Path(inner) => find_and_combine_idents(inner), + ExtType::Reference(inner) => { + let name = inner_self_ty(&(&*inner.elem).into()); + if inner.mutability.is_some() { + format_ident!("RefMut{}", name) + } else { + format_ident!("Ref{}", name) + } + } + ExtType::Array(inner) => { + let name = inner_self_ty(&(&*inner.elem).into()); + format_ident!("ListOf{}", name) + } + ExtType::Group(inner) => { + let name = inner_self_ty(&(&*inner.elem).into()); + format_ident!("Group{}", name) + } + ExtType::Paren(inner) => { + let name = inner_self_ty(&(&*inner.elem).into()); + format_ident!("Paren{}", name) + } + ExtType::Ptr(inner) => { + let name = inner_self_ty(&(&*inner.elem).into()); + format_ident!("PointerTo{}", name) + } + ExtType::Slice(inner) => { + let name = inner_self_ty(&(&*inner.elem).into()); + format_ident!("SliceOf{}", name) + } + ExtType::Tuple(inner) => { + let mut name = format_ident!("TupleOf"); + for elem in &inner.elems { + name = format_ident!("{}{}", name, inner_self_ty(&elem.into())); + } + name + } + ExtType::Never(_) => format_ident!("Never"), + ExtType::BareFn(inner) => { + let mut name = format_ident!("BareFn"); + for input in inner.inputs.iter() { + name = format_ident!("{}{}", name, inner_self_ty(&(&input.ty).into())); + } + match &inner.output { + syn::ReturnType::Default => { + name = format_ident!("{}Unit", name); + } + syn::ReturnType::Type(_, ty) => { + name = format_ident!("{}{}", name, inner_self_ty(&(&**ty).into())); + } + } + name + } + ExtType::TraitObject(inner) => { + let mut name = format_ident!("TraitObject"); + for bound in inner.bounds.iter() { + match bound { + TypeParamBound::Trait(bound) => { + for segment in bound.path.segments.iter() { + name = format_ident!("{}{}", name, segment.ident); + } + } + TypeParamBound::Lifetime(lifetime) => { + name = format_ident!("{}{}", name, lifetime.ident); + } + } + } + name + } + } + } + + format_ident!("{}Ext", inner_self_ty(self_ty)) +} + +fn find_and_combine_idents(type_path: &TypePath) -> Ident { + use syn::visit::{self, Visit}; + + struct IdentVisitor<'a>(Vec<&'a Ident>); + + impl<'a> Visit<'a> for IdentVisitor<'a> { + fn visit_ident(&mut self, i: &'a Ident) { + self.0.push(i); + } + } + + let mut visitor = IdentVisitor(Vec::new()); + visit::visit_type_path(&mut visitor, type_path); + let idents = visitor.0; + + if idents.is_empty() { + abort!(type_path.span(), "Empty type path") + } else { + let start = &idents[0].span(); + let combined_span = idents + .iter() + .map(|i| i.span()) + .fold(*start, |a, b| a.join(b).unwrap_or(a)); + + let combined_name = idents.iter().map(|i| i.to_string()).collect::<String>(); + + Ident::new(&combined_name, combined_span) + } +} + +#[derive(Debug, Default)] +struct MethodsAndConsts { + trait_methods: Vec<TraitItemMethod>, + trait_consts: Vec<TraitItemConst>, +} + +#[allow(clippy::wildcard_in_or_patterns)] +fn extract_allowed_items(items: &[ImplItem]) -> MethodsAndConsts { + let mut acc = MethodsAndConsts::default(); + for item in items { + match item { + ImplItem::Method(method) => acc.trait_methods.push(TraitItemMethod { + attrs: method.attrs.clone(), + sig: method.sig.clone(), + default: None, + semi_token: Some(Semi::default()), + }), + ImplItem::Const(const_) => acc.trait_consts.push(TraitItemConst { + attrs: const_.attrs.clone(), + const_token: Default::default(), + ident: const_.ident.clone(), + colon_token: Default::default(), + ty: const_.ty.clone(), + default: None, + semi_token: Default::default(), + }), + ImplItem::Type(_) => abort!( + item.span(), + "Associated types are not allowed in #[ext] impls" + ), + ImplItem::Macro(_) => abort!(item.span(), "Macros are not allowed in #[ext] impls"), + ImplItem::Verbatim(_) | _ => abort!(item.span(), "Not allowed in #[ext] impls"), + } + } + acc +} + +#[derive(Debug)] +struct Config { + ext_trait_name: Option<Ident>, + visibility: Visibility, + supertraits: Option<Punctuated<TypeParamBound, Add>>, +} + +impl Parse for Config { + fn parse(input: ParseStream) -> parse::Result<Self> { + let mut config = Config::default(); + + if let Ok(visibility) = input.parse::<Visibility>() { + config.visibility = visibility; + } + + input.parse::<Token![,]>().ok(); + + while !input.is_empty() { + let ident = input.parse::<Ident>()?; + input.parse::<Token![=]>()?; + + match &*ident.to_string() { + "name" => { + config.ext_trait_name = Some(input.parse()?); + } + "supertraits" => { + config.supertraits = + Some(Punctuated::<TypeParamBound, Add>::parse_terminated(input)?); + } + _ => abort!(ident.span(), "Unknown configuration name"), + } + + input.parse::<Token![,]>().ok(); + } + + Ok(config) + } +} + +impl Default for Config { + fn default() -> Self { + Self { + ext_trait_name: None, + visibility: Visibility::Inherited, + supertraits: None, + } + } +} + +fn punctuated_from_iter<I, T, P>(i: I) -> Punctuated<T, P> +where + P: Default, + I: IntoIterator<Item = T>, +{ + let mut iter = i.into_iter().peekable(); + let mut acc = Punctuated::default(); + + while let Some(item) = iter.next() { + acc.push_value(item); + + if iter.peek().is_some() { + acc.push_punct(P::default()); + } + } + + acc +} + +#[cfg(test)] +mod test { + #[allow(unused_imports)] + use super::*; + + #[test] + fn test_ui() { + let t = trybuild::TestCases::new(); + t.pass("tests/compile_pass/*.rs"); + t.compile_fail("tests/compile_fail/*.rs"); + } +} diff --git a/third_party/rust/extend/tests/compile_fail/double_vis.rs b/third_party/rust/extend/tests/compile_fail/double_vis.rs new file mode 100644 index 0000000000..4e0e135ad3 --- /dev/null +++ b/third_party/rust/extend/tests/compile_fail/double_vis.rs @@ -0,0 +1,14 @@ +mod a { + use extend::ext; + + #[ext(pub(super))] + pub impl i32 { + fn foo() -> Foo { + Foo + } + } + + pub struct Foo; +} + +fn main() {} diff --git a/third_party/rust/extend/tests/compile_fail/double_vis.stderr b/third_party/rust/extend/tests/compile_fail/double_vis.stderr new file mode 100644 index 0000000000..c98b614935 --- /dev/null +++ b/third_party/rust/extend/tests/compile_fail/double_vis.stderr @@ -0,0 +1,5 @@ +error: Cannot set visibility on `#[ext]` and `impl` block + --> $DIR/double_vis.rs:4:11 + | +4 | #[ext(pub(super))] + | ^^^ diff --git a/third_party/rust/extend/tests/compile_fail/supertraits_are_actually_included.rs b/third_party/rust/extend/tests/compile_fail/supertraits_are_actually_included.rs new file mode 100644 index 0000000000..5fba303138 --- /dev/null +++ b/third_party/rust/extend/tests/compile_fail/supertraits_are_actually_included.rs @@ -0,0 +1,14 @@ +use extend::ext; + +trait MyTrait {} + +#[ext(supertraits = MyTrait)] +impl String { + fn my_len(&self) -> usize { + self.len() + } +} + +fn main() { + assert_eq!(String::new().my_len(), 0); +} diff --git a/third_party/rust/extend/tests/compile_fail/supertraits_are_actually_included.stderr b/third_party/rust/extend/tests/compile_fail/supertraits_are_actually_included.stderr new file mode 100644 index 0000000000..60059f73fb --- /dev/null +++ b/third_party/rust/extend/tests/compile_fail/supertraits_are_actually_included.stderr @@ -0,0 +1,10 @@ +error[E0277]: the trait bound `String: MyTrait` is not satisfied + --> $DIR/supertraits_are_actually_included.rs:6:6 + | +5 | #[ext(supertraits = MyTrait)] + | ------- required by this bound in `StringExt` +6 | impl String { + | ^^^^^^ + | | + | the trait `MyTrait` is not implemented for `String` + | required by a bound in this diff --git a/third_party/rust/extend/tests/compile_pass/associated_constants.rs b/third_party/rust/extend/tests/compile_pass/associated_constants.rs new file mode 100644 index 0000000000..5cb982c6a4 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/associated_constants.rs @@ -0,0 +1,10 @@ +use extend::ext; + +#[ext] +impl Option<String> { + const FOO: usize = 1; +} + +fn main() { + assert_eq!(Option::<String>::FOO, 1); +} diff --git a/third_party/rust/extend/tests/compile_pass/async_trait.rs b/third_party/rust/extend/tests/compile_pass/async_trait.rs new file mode 100644 index 0000000000..067bdc9bb7 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/async_trait.rs @@ -0,0 +1,25 @@ +use extend::ext; +use async_trait::async_trait; + +#[ext] +#[async_trait] +impl String { + async fn foo() -> usize { + 1 + } +} + +#[ext] +#[async_trait] +pub impl i32 { + async fn bar() -> usize { + 1 + } +} + +async fn foo() { + let _: usize = String::foo().await; + let _: usize = i32::bar().await; +} + +fn main() {} diff --git a/third_party/rust/extend/tests/compile_pass/changing_extension_trait_name.rs b/third_party/rust/extend/tests/compile_pass/changing_extension_trait_name.rs new file mode 100644 index 0000000000..fc83b19339 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/changing_extension_trait_name.rs @@ -0,0 +1,10 @@ +use extend::ext; + +#[ext(name = Foo)] +impl i32 { + fn foo() {} +} + +fn main() { + <i32 as Foo>::foo(); +} diff --git a/third_party/rust/extend/tests/compile_pass/complex_trait_name.rs b/third_party/rust/extend/tests/compile_pass/complex_trait_name.rs new file mode 100644 index 0000000000..3457495132 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/complex_trait_name.rs @@ -0,0 +1,16 @@ +mod foo { + use extend::ext; + + #[ext(pub)] + impl<T1, T2, T3> (T1, T2, T3) { + fn size(&self) -> usize { + 3 + } + } +} + +fn main() { + use foo::TupleOfT1T2T3Ext; + + assert_eq!(3, (0, 0, 0).size()); +} diff --git a/third_party/rust/extend/tests/compile_pass/double_ext_on_same_type.rs b/third_party/rust/extend/tests/compile_pass/double_ext_on_same_type.rs new file mode 100644 index 0000000000..972d530fa8 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/double_ext_on_same_type.rs @@ -0,0 +1,17 @@ +use extend::ext; + +#[ext] +impl Option<usize> { + fn foo() -> usize { + 1 + } +} + +#[ext] +impl Option<i32> { + fn bar() -> i32 { + 1 + } +} + +fn main() {} diff --git a/third_party/rust/extend/tests/compile_pass/extension_on_complex_types.rs b/third_party/rust/extend/tests/compile_pass/extension_on_complex_types.rs new file mode 100644 index 0000000000..20dfb7ca90 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/extension_on_complex_types.rs @@ -0,0 +1,58 @@ +use extend::ext; + +#[ext] +impl<'a> &'a str { + fn foo(self) {} +} + +#[ext] +impl<T> [T; 3] { + fn foo(self) {} +} + +#[ext] +impl *const i32 { + fn foo(self) {} +} + +#[ext] +impl<T> [T] { + fn foo(&self) {} +} + +#[ext] +impl<'a, T> &'a [T] { + fn foo(self) {} +} + +#[ext] +impl (i32, i64) { + fn foo(self) {} +} + +#[ext] +impl fn(i32) -> bool { + fn foo(self) {} +} + +fn bare_fn(_: i32) -> bool { + false +} + +#[ext] +impl dyn Send + Sync + 'static {} + +fn main() { + "".foo(); + + [1, 2, 3].foo(); + + let ptr: *const i32 = &123; + ptr.foo(); + + &[1, 2, 3].foo(); + + (1i32, 1i64).foo(); + + (bare_fn as fn(i32) -> bool).foo(); +} diff --git a/third_party/rust/extend/tests/compile_pass/generics.rs b/third_party/rust/extend/tests/compile_pass/generics.rs new file mode 100644 index 0000000000..4c56b14677 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/generics.rs @@ -0,0 +1,15 @@ +use extend::ext; + +#[ext] +impl<'a, T: Clone> Vec<&'a T> +where + T: 'a + Copy, +{ + fn size(&self) -> usize { + self.len() + } +} + +fn main() { + assert_eq!(3, vec![&1, &2, &3].size()); +} diff --git a/third_party/rust/extend/tests/compile_pass/hello_world.rs b/third_party/rust/extend/tests/compile_pass/hello_world.rs new file mode 100644 index 0000000000..75c61183b4 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/hello_world.rs @@ -0,0 +1,20 @@ +use extend::ext; + +#[ext] +impl i32 { + fn add_one(&self) -> Self { + self + 1 + } + + fn foo() -> MyType { + MyType + } +} + +#[derive(Debug, Eq, PartialEq)] +struct MyType; + +fn main() { + assert_eq!(i32::foo(), MyType); + assert_eq!(1.add_one(), 2); +} diff --git a/third_party/rust/extend/tests/compile_pass/issue_2.rs b/third_party/rust/extend/tests/compile_pass/issue_2.rs new file mode 100644 index 0000000000..76516486ab --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/issue_2.rs @@ -0,0 +1,33 @@ +#![allow(unused_variables)] + +use extend::ext; +use std::iter::FromIterator; + +#[ext] +impl<T, K, F, C> C +where + C: IntoIterator<Item = T>, + K: Eq, + F: Fn(&T) -> K, +{ + fn group_by<Out>(self, f: F) -> Out + where + Out: FromIterator<(K, Vec<T>)>, + { + todo!() + } + + fn group_by_and_map_values<Out, G, T2>(self, f: F, g: G) -> Out + where + G: Fn(T) -> T2 + Copy, + Out: FromIterator<(K, Vec<T2>)>, + { + todo!() + } + + fn group_by_and_return_groups(self, f: F) -> Vec<Vec<T>> { + todo!() + } +} + +fn main() {} diff --git a/third_party/rust/extend/tests/compile_pass/more_than_one_extension.rs b/third_party/rust/extend/tests/compile_pass/more_than_one_extension.rs new file mode 100644 index 0000000000..e5ce539f15 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/more_than_one_extension.rs @@ -0,0 +1,13 @@ +use extend::ext; + +#[ext] +impl i32 { + fn foo() {} +} + +#[ext] +impl i64 { + fn bar() {} +} + +fn main() {} diff --git a/third_party/rust/extend/tests/compile_pass/multiple_config.rs b/third_party/rust/extend/tests/compile_pass/multiple_config.rs new file mode 100644 index 0000000000..9ffdad1316 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/multiple_config.rs @@ -0,0 +1,13 @@ +use extend::ext; + +#[ext(pub(crate), name = Foo)] +impl i32 { + fn foo() {} +} + +#[ext(pub, name = Bar)] +impl i64 { + fn foo() {} +} + +fn main() {} diff --git a/third_party/rust/extend/tests/compile_pass/multiple_generic_params.rs b/third_party/rust/extend/tests/compile_pass/multiple_generic_params.rs new file mode 100644 index 0000000000..29e4b9ca21 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/multiple_generic_params.rs @@ -0,0 +1,11 @@ +use extend::ext; +use std::marker::PhantomData; + +struct Foo<T>(PhantomData<T>); + +#[ext] +impl<T, K> T { + fn some_method(&self, _: Foo<K>) {} +} + +fn main() {} diff --git a/third_party/rust/extend/tests/compile_pass/pub_impl.rs b/third_party/rust/extend/tests/compile_pass/pub_impl.rs new file mode 100644 index 0000000000..f7bff519c0 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/pub_impl.rs @@ -0,0 +1,15 @@ +mod a { + use extend::ext; + + #[ext] + pub impl i32 { + fn foo() -> Foo { Foo } + } + + pub struct Foo; +} + +fn main() { + use a::i32Ext; + i32::foo(); +} diff --git a/third_party/rust/extend/tests/compile_pass/ref_and_ref_mut.rs b/third_party/rust/extend/tests/compile_pass/ref_and_ref_mut.rs new file mode 100644 index 0000000000..156f62027d --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/ref_and_ref_mut.rs @@ -0,0 +1,13 @@ +use extend::ext; + +#[ext] +impl &i32 { + fn foo() {} +} + +#[ext] +impl &mut i32 { + fn bar() {} +} + +fn main() {} diff --git a/third_party/rust/extend/tests/compile_pass/sized.rs b/third_party/rust/extend/tests/compile_pass/sized.rs new file mode 100644 index 0000000000..dae05f3f52 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/sized.rs @@ -0,0 +1,36 @@ +use extend::ext_sized; + +#[ext_sized(name = One)] +impl i32 { + fn requires_sized(self) -> Option<Self> { + Some(self) + } +} + +#[ext_sized(name = Two, supertraits = Default)] +impl i32 { + fn with_another_supertrait(self) -> Option<Self> { + Some(self) + } +} + +#[ext_sized(name = Three, supertraits = Default + Clone + Copy)] +impl i32 { + fn multiple_supertraits(self) -> Option<Self> { + Some(self) + } +} + +#[ext_sized(name = Four, supertraits = Sized)] +impl i32 { + fn already_sized(self) -> Option<Self> { + Some(self) + } +} + +fn main() { + 1.requires_sized(); + 1.with_another_supertrait(); + 1.multiple_supertraits(); + 1.already_sized(); +} diff --git a/third_party/rust/extend/tests/compile_pass/super_trait.rs b/third_party/rust/extend/tests/compile_pass/super_trait.rs new file mode 100644 index 0000000000..30b147d24a --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/super_trait.rs @@ -0,0 +1,16 @@ +use extend::ext; + +trait MyTrait {} + +impl MyTrait for String {} + +#[ext(supertraits = Default + Clone + MyTrait)] +impl String { + fn my_len(&self) -> usize { + self.len() + } +} + +fn main() { + assert_eq!(String::new().my_len(), 0); +} diff --git a/third_party/rust/extend/tests/compile_pass/visibility_config.rs b/third_party/rust/extend/tests/compile_pass/visibility_config.rs new file mode 100644 index 0000000000..bcb8b19576 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/visibility_config.rs @@ -0,0 +1,15 @@ +mod a { + use extend::ext; + + #[ext(pub)] + impl i32 { + fn foo() -> Foo { Foo } + } + + pub struct Foo; +} + +fn main() { + use a::i32Ext; + i32::foo(); +} |