diff options
Diffstat (limited to 'third_party/rust/extend')
29 files changed, 1264 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..fc008ab608 --- /dev/null +++ b/third_party/rust/extend/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"7f91119554e4587936d7e431dae1b7b77c677230809615ad81e931477e7b7013","Cargo.toml":"fa01cfc6e053fa0072da1ad9e413a147a4f2cfee53a529de57a7c44ce858ad13","LICENSE":"db0efd0bb80126f32214f478434536d07d86a30658fb209d8b543b7261492a2b","README.md":"0872b2b15ec22732a09b121a31cdd8f8cfc80566f671566e4a7fb6b52a742a2e","deny.toml":"5896a0134be617fcfbde7680ae65f0d8bbb14e7250588f27d8bff347f7bd59d3","src/lib.rs":"6128d331b2ee8fb6261548387b691c633b515a3d6dec1533cac69035676c11ef","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":"d88d744cd810a58b3f7a4495ecb020604ddc1badfb765a7b13bd38ba1ba0f243","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/destructure.rs":"274b1c97ecce02fd3412a4da3f194e48dc1339a7714ebf3748b0d9ff16ce93be","tests/compile_pass/double_ext_on_same_type.rs":"0e4d16fe9059f0325e4ad1337d53738876633b0533f9a2d4e9714ddada96eb4d","tests/compile_pass/extension_on_complex_types.rs":"8dd484df9e6a4c8a20b9f14b8d33aeecb8fce6a9e1be2e68b8dc7f312e860562","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":"311a6d2f1f9d60bff73d2c78a0af97ed27f79672f15c238192a5bbb64db56d00"}
\ 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..f1f23e25c2 --- /dev/null +++ b/third_party/rust/extend/CHANGELOG.md @@ -0,0 +1,87 @@ +# 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.2.0 - 2023-03-18 + +- Support destructuring function arguments + +## 1.1.3 - 2023-03-18 + +- Update to syn 2.0 + +## 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..8e33ec31a6 --- /dev/null +++ b/third_party/rust/extend/Cargo.toml @@ -0,0 +1,49 @@ +# 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" +name = "extend" +version = "1.2.0" +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" +readme = "README.md" +keywords = [ + "extension", + "trait", +] +categories = ["rust-patterns"] +license = "MIT" +repository = "https://github.com/davidpdrsn/extend" + +[lib] +proc-macro = true + +[dependencies.proc-macro2] +version = "1" + +[dependencies.quote] +version = "1" + +[dependencies.syn] +version = "2" +features = [ + "full", + "extra-traits", + "visit", +] + +[dev-dependencies.async-trait] +version = "0.1.40" + +[dev-dependencies.trybuild] +version = "1.0.17" 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..2661f6d553 --- /dev/null +++ b/third_party/rust/extend/deny.toml @@ -0,0 +1,23 @@ +[advisories] +vulnerability = "deny" +unmaintained = "warn" +notice = "warn" +ignore = [] + +[licenses] +unlicensed = "warn" +allow = [] +deny = [] +copyleft = "warn" +allow-osi-fsf-free = "either" +confidence-threshold = 0.8 + +[bans] +multiple-versions = "deny" +highlight = "all" +skip-tree = [] + +[sources] +unknown-registry = "warn" +unknown-git = "warn" +allow-git = [] diff --git a/third_party/rust/extend/src/lib.rs b/third_party/rust/extend/src/lib.rs new file mode 100644 index 0000000000..5081824ff8 --- /dev/null +++ b/third_party/rust/extend/src/lib.rs @@ -0,0 +1,657 @@ +//! 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 +//! } +//! } +//! +//! 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 + +#![allow(clippy::let_and_return)] +#![deny(unused_variables, dead_code, unused_must_use, unused_imports)] + +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; +use std::convert::{TryFrom, TryInto}; +use syn::{ + parse::{self, Parse, ParseStream}, + parse_macro_input, parse_quote, + punctuated::Punctuated, + spanned::Spanned, + token::{Plus, Semi}, + Ident, ImplItem, ItemImpl, Result, Token, TraitItemConst, TraitItemFn, 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] +#[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); + match go(item, config) { + Ok(tokens) => tokens, + Err(err) => err.into_compile_error().into(), + } +} + +/// 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] +#[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)) + }; + + match go(item, config) { + Ok(tokens) => tokens, + Err(err) => err.into_compile_error().into(), + } +} + +fn go(item: Input, mut config: Config) -> Result<proc_macro::TokenStream> { + if let Some(vis) = item.vis { + if config.visibility != Visibility::Inherited { + return Err(syn::Error::new( + 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_ { + return Err(syn::Error::new( + path.span(), + "Trait impls cannot be used for #[ext]", + )); + } + + let self_ty = parse_self_ty(&self_ty)?; + + let ext_trait_name = if let Some(ext_trait_name) = config.ext_trait_name { + ext_trait_name + } 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::<_, _, Plus>(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(); + + Ok(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) -> Result<ExtType> { + let ty = 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(_) | _ => { + return Err(syn::Error::new( + self_ty.span(), + "#[ext] is not supported for this kind of type", + )) + } + }; + Ok(ty) +} + +impl<'a> TryFrom<&'a Type> for ExtType<'a> { + type Error = syn::Error; + + fn try_from(inner: &'a Type) -> Result<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) -> Result<Ident> { + fn inner_self_ty(self_ty: &ExtType) -> Result<Ident> { + match self_ty { + ExtType::Path(inner) => find_and_combine_idents(inner), + ExtType::Reference(inner) => { + let name = inner_self_ty(&(&*inner.elem).try_into()?)?; + if inner.mutability.is_some() { + Ok(format_ident!("RefMut{}", name)) + } else { + Ok(format_ident!("Ref{}", name)) + } + } + ExtType::Array(inner) => { + let name = inner_self_ty(&(&*inner.elem).try_into()?)?; + Ok(format_ident!("ListOf{}", name)) + } + ExtType::Group(inner) => { + let name = inner_self_ty(&(&*inner.elem).try_into()?)?; + Ok(format_ident!("Group{}", name)) + } + ExtType::Paren(inner) => { + let name = inner_self_ty(&(&*inner.elem).try_into()?)?; + Ok(format_ident!("Paren{}", name)) + } + ExtType::Ptr(inner) => { + let name = inner_self_ty(&(&*inner.elem).try_into()?)?; + Ok(format_ident!("PointerTo{}", name)) + } + ExtType::Slice(inner) => { + let name = inner_self_ty(&(&*inner.elem).try_into()?)?; + Ok(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.try_into()?)?); + } + Ok(name) + } + ExtType::Never(_) => Ok(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).try_into()?)?); + } + match &inner.output { + syn::ReturnType::Default => { + name = format_ident!("{}Unit", name); + } + syn::ReturnType::Type(_, ty) => { + name = format_ident!("{}{}", name, inner_self_ty(&(&**ty).try_into()?)?); + } + } + Ok(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); + } + other => { + return Err(syn::Error::new(other.span(), "unsupported bound")); + } + } + } + Ok(name) + } + } + } + + Ok(format_ident!("{}Ext", inner_self_ty(self_ty)?)) +} + +fn find_and_combine_idents(type_path: &TypePath) -> Result<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() { + Err(syn::Error::new(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>(); + + Ok(Ident::new(&combined_name, combined_span)) + } +} + +#[derive(Debug, Default)] +struct MethodsAndConsts { + trait_methods: Vec<TraitItemFn>, + trait_consts: Vec<TraitItemConst>, +} + +#[allow(clippy::wildcard_in_or_patterns)] +fn extract_allowed_items(items: &[ImplItem]) -> Result<MethodsAndConsts> { + let mut acc = MethodsAndConsts::default(); + for item in items { + match item { + ImplItem::Fn(method) => acc.trait_methods.push(TraitItemFn { + attrs: method.attrs.clone(), + sig: { + let mut sig = method.sig.clone(); + sig.inputs = sig + .inputs + .into_iter() + .map(|fn_arg| match fn_arg { + syn::FnArg::Receiver(recv) => syn::FnArg::Receiver(recv), + syn::FnArg::Typed(mut pat_type) => { + pat_type.pat = Box::new(match *pat_type.pat { + syn::Pat::Ident(pat_ident) => syn::Pat::Ident(pat_ident), + _ => { + parse_quote!(_) + } + }); + syn::FnArg::Typed(pat_type) + } + }) + .collect(); + sig + }, + default: None, + semi_token: Some(Semi::default()), + }), + ImplItem::Const(const_) => acc.trait_consts.push(TraitItemConst { + attrs: const_.attrs.clone(), + generics: const_.generics.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(_) => { + return Err(syn::Error::new( + item.span(), + "Associated types are not allowed in #[ext] impls", + )) + } + ImplItem::Macro(_) => { + return Err(syn::Error::new( + item.span(), + "Macros are not allowed in #[ext] impls", + )) + } + ImplItem::Verbatim(_) | _ => { + return Err(syn::Error::new(item.span(), "Not allowed in #[ext] impls")) + } + } + } + Ok(acc) +} + +#[derive(Debug)] +struct Config { + ext_trait_name: Option<Ident>, + visibility: Visibility, + supertraits: Option<Punctuated<TypeParamBound, Plus>>, +} + +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, Plus>::parse_terminated(input)?); + } + _ => return Err(syn::Error::new(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..5dc91118aa --- /dev/null +++ b/third_party/rust/extend/tests/compile_fail/supertraits_are_actually_included.stderr @@ -0,0 +1,13 @@ +error[E0277]: the trait bound `String: MyTrait` is not satisfied + --> tests/compile_fail/supertraits_are_actually_included.rs:6:6 + | +6 | impl String { + | ^^^^^^ the trait `MyTrait` is not implemented for `String` + | +note: required by a bound in `StringExt` + --> tests/compile_fail/supertraits_are_actually_included.rs:5:21 + | +5 | #[ext(supertraits = MyTrait)] + | ^^^^^^^ required by this bound in `StringExt` +6 | impl 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/destructure.rs b/third_party/rust/extend/tests/compile_pass/destructure.rs new file mode 100644 index 0000000000..bdc412cb0e --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/destructure.rs @@ -0,0 +1,12 @@ +#![allow(warnings)] + +use extend::ext; + +#[ext] +impl i32 { + fn foo(self, (a, b): (i32, i32)) {} + + fn bar(self, [a, b]: [i32; 2]) {} +} + +fn main() {} 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..e8d77b40a9 --- /dev/null +++ b/third_party/rust/extend/tests/compile_pass/extension_on_complex_types.rs @@ -0,0 +1,60 @@ +#![allow(warnings)] + +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(); +} |