libcst_derive-0.1.0/Cargo.toml0000644000000015650000000000100116550ustar # 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 = "libcst_derive" version = "0.1.0" description = "Proc macro helpers for libcst." homepage = "https://github.com/Instagram/LibCST" documentation = "https://libcst.rtfd.org" keywords = [ "macros", "python", ] license-file = "LICENSE" [lib] proc-macro = true [dependencies.quote] version = "1.0" [dependencies.syn] version = "1.0" [dev-dependencies.trybuild] version = "1.0" libcst_derive-0.1.0/Cargo.toml.orig000064400000000000000000000005650072674642500153650ustar 00000000000000[package] name = "libcst_derive" version = "0.1.0" edition = "2018" description = "Proc macro helpers for libcst." license-file = "../../LICENSE" homepage = "https://github.com/Instagram/LibCST" documentation = "https://libcst.rtfd.org" keywords = ["macros", "python"] [lib] proc-macro = true [dependencies] syn = "1.0" quote = "1.0" [dev-dependencies] trybuild = "1.0" libcst_derive-0.1.0/LICENSE000064400000000000000000000111320072674642500134730ustar 00000000000000All contributions towards LibCST are MIT licensed. Some Python files have been derived from the standard library and are therefore PSF licensed. Modifications on these files are dual licensed (both MIT and PSF). These files are: - libcst/_parser/base_parser.py - libcst/_parser/parso/utils.py - libcst/_parser/parso/pgen2/generator.py - libcst/_parser/parso/pgen2/grammar_parser.py - libcst/_parser/parso/python/py_token.py - libcst/_parser/parso/python/tokenize.py - libcst/_parser/parso/tests/test_fstring.py - libcst/_parser/parso/tests/test_tokenize.py - libcst/_parser/parso/tests/test_utils.py - libcst_native/src/tokenize/core/mod.rs - libcst_native/src/tokenize/core/string_types.rs Some Python files have been taken from dataclasses and are therefore Apache licensed. Modifications on these files are licensed under Apache 2.0 license. These files are: - libcst/_add_slots.py ------------------------------------------------------------------------------- MIT License Copyright (c) Meta Platforms, Inc. and affiliates. 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 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. ------------------------------------------------------------------------------- PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. ------------------------------------------------------------------------------- APACHE LICENSE, VERSION 2.0 http://www.apache.org/licenses/LICENSE-2.0 libcst_derive-0.1.0/src/codegen.rs000064400000000000000000000042240072674642500152330ustar 00000000000000// Copyright (c) Meta Platforms, Inc. and affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree use proc_macro::TokenStream; use quote::{quote, quote_spanned}; use syn::{self, spanned::Spanned, Data, DataEnum, DeriveInput, Fields, FieldsUnnamed}; pub(crate) fn impl_codegen(ast: &DeriveInput) -> TokenStream { match &ast.data { Data::Enum(e) => impl_enum(ast, e), Data::Struct(s) => quote_spanned! { s.struct_token.span() => compile_error!("Struct type is not supported") } .into(), Data::Union(u) => quote_spanned! { u.union_token.span() => compile_error!("Union type is not supported") } .into(), } } fn impl_enum(ast: &DeriveInput, e: &DataEnum) -> TokenStream { let mut varnames = vec![]; for var in e.variants.iter() { match &var.fields { Fields::Named(n) => { return quote_spanned! { n.span() => compile_error!("Named enum fields not supported") } .into() } f @ Fields::Unit => { return quote_spanned! { f.span() => compile_error!("Empty enum variants not supported") } .into() } Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { if unnamed.len() > 1 { return quote_spanned! { unnamed.span() => compile_error!("Multiple unnamed fields not supported") } .into(); } varnames.push(&var.ident); } } } let ident = &ast.ident; let generics = &ast.generics; let gen = quote! { impl<'a> Codegen<'a> for #ident #generics { fn codegen(&self, state: &mut CodegenState<'a>) { match self { #(Self::#varnames(x) => x.codegen(state),)* } } } }; gen.into() } libcst_derive-0.1.0/src/cstnode.rs000064400000000000000000000324370072674642500152750ustar 00000000000000// Copyright (c) Meta Platforms, Inc. and affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree use proc_macro::TokenStream; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ self, parse::{Parse, ParseStream}, parse_quote, punctuated::{Pair, Punctuated}, spanned::Spanned, token::Comma, AngleBracketedGenericArguments, Attribute, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, FieldsNamed, FieldsUnnamed, GenericArgument, Generics, Ident, Meta, MetaList, NestedMeta, Path, PathArguments, PathSegment, Token, Type, TypePath, Visibility, }; pub(crate) struct CSTNodeParams { traits: Punctuated, } #[derive(PartialEq, Eq)] enum SupportedTrait { ParenthesizedNode, Codegen, Inflate, NoIntoPy, Default, } pub(crate) fn impl_cst_node(ast: DeriveInput, args: CSTNodeParams) -> TokenStream { match ast.data { Data::Enum(e) => impl_enum(args, ast.attrs, ast.vis, ast.ident, ast.generics, e), Data::Struct(s) => impl_struct(args, ast.attrs, ast.vis, ast.ident, ast.generics, s), Data::Union(u) => quote_spanned! { u.union_token.span() => compile_error!("Union type is not supported") } .into(), } } impl CSTNodeParams { fn has_trait(&self, treyt: &SupportedTrait) -> bool { self.traits.iter().any(|x| x == treyt) } } impl Parse for SupportedTrait { fn parse(input: ParseStream) -> syn::Result { if input.peek(Ident) { let id: Ident = input.parse()?; return match id.to_string().as_str() { "ParenthesizedNode" => Ok(Self::ParenthesizedNode), "Codegen" => Ok(Self::Codegen), "Inflate" => Ok(Self::Inflate), "NoIntoPy" => Ok(Self::NoIntoPy), "Default" => Ok(Self::Default), _ => Err(input.error("Not a supported trait to derive for cst_node")), }; } Err(input.error("Pass in trait names to be derived")) } } impl Parse for CSTNodeParams { fn parse(input: ParseStream) -> syn::Result { Ok(Self { traits: input.parse_terminated(SupportedTrait::parse)?, }) } } // enum Foo<'a> { // Variant(Box>), // } // => // enum Foo<'a> { // Variant(Box>), // } // enum DeflatedFoo<'r, 'a> { // Variant(Box>), // } fn impl_enum( args: CSTNodeParams, mut attrs: Vec, vis: Visibility, ident: Ident, generics: Generics, mut e: DataEnum, ) -> TokenStream { let deflated_vis = vis.clone(); let deflated_ident = format_ident!("Deflated{}", &ident); let deflated_generics: Generics = parse_quote!(<'r, 'a>); let mut deflated_variant_tokens = vec![]; for var in e.variants.iter_mut() { let (inflated_fields, deflated_fields) = impl_fields(var.fields.clone()); var.fields = deflated_fields; deflated_variant_tokens.push(var.to_token_stream()); var.fields = inflated_fields; } add_inflated_attrs(&args, &mut attrs); let inflated = DeriveInput { attrs, vis, ident, generics, data: Data::Enum(e), }; let deflated_attrs = get_deflated_attrs(&args); let gen = quote! { #[derive(Debug, PartialEq, Eq, Clone)] #inflated #[derive(Debug, PartialEq, Eq, Clone)] #(#deflated_attrs)* #deflated_vis enum #deflated_ident#deflated_generics { #(#deflated_variant_tokens,)* } }; gen.into() } fn get_deflated_attrs(args: &CSTNodeParams) -> Vec { let mut deflated_attrs: Vec = vec![]; if args.has_trait(&SupportedTrait::Inflate) { deflated_attrs.push(parse_quote!(#[derive(Inflate)])); } if args.has_trait(&SupportedTrait::ParenthesizedNode) { deflated_attrs.push(parse_quote!(#[derive(ParenthesizedDeflatedNode)])) } if args.has_trait(&SupportedTrait::Default) { deflated_attrs.push(parse_quote!(#[derive(Default)])); } deflated_attrs } fn add_inflated_attrs(args: &CSTNodeParams, attrs: &mut Vec) { if args.has_trait(&SupportedTrait::Codegen) { attrs.push(parse_quote!(#[derive(Codegen)])); } if args.has_trait(&SupportedTrait::ParenthesizedNode) { attrs.push(parse_quote!(#[derive(ParenthesizedNode)])); } if args.has_trait(&SupportedTrait::Default) { attrs.push(parse_quote!(#[derive(Default)])); } if !args.has_trait(&SupportedTrait::NoIntoPy) { attrs.push(parse_quote!(#[cfg_attr(feature = "py", derive(TryIntoPy))])); } } // pub struct Foo<'a> { // pub bar: Bar<'a>, // pub value: &'a str, // pub whitespace_after: SimpleWhitespace<'a>, // pub(crate) tok: Option, // } // => // pub struct Foo<'a> { // pub bar: Bar<'a>, // pub value: &'a str, // pub whitespace_after: SimpleWhitespace<'a>, // } // struct DeflatedFoo<'r, 'a> { // pub bar: DeflatedBar<'r, 'a>, // pub value: &'a str, // pub tok: Option> // } fn impl_struct( args: CSTNodeParams, mut attrs: Vec, vis: Visibility, ident: Ident, generics: Generics, mut s: DataStruct, ) -> TokenStream { let deflated_vis = vis.clone(); let deflated_ident = format_ident!("Deflated{}", &ident); let deflated_generics: Generics = parse_quote!(<'r, 'a>); let (inflated_fields, deflated_fields) = impl_fields(s.fields); s.fields = inflated_fields; add_inflated_attrs(&args, &mut attrs); let inflated = DeriveInput { attrs, vis, ident, generics, data: Data::Struct(s), }; let deflated_attrs = get_deflated_attrs(&args); let gen = quote! { #[derive(Debug, PartialEq, Eq, Clone)] #inflated #[derive(Debug, PartialEq, Eq, Clone)] #(#deflated_attrs)* #deflated_vis struct #deflated_ident#deflated_generics #deflated_fields }; gen.into() } fn impl_fields(fields: Fields) -> (Fields, Fields) { match &fields { Fields::Unnamed(fs) => { let deflated_fields = impl_unnamed_fields(fs.clone()); (fields, Fields::Unnamed(deflated_fields)) } Fields::Named(fs) => impl_named_fields(fs.clone()), Fields::Unit => (Fields::Unit, Fields::Unit), } } fn impl_unnamed_fields(mut deflated_fields: FieldsUnnamed) -> FieldsUnnamed { let mut added_lifetime = false; deflated_fields.unnamed = deflated_fields .unnamed .into_pairs() .map(|pair| { let (deflated, lifetime) = make_into_deflated(pair); added_lifetime |= lifetime; deflated }) .collect(); // Make sure all Deflated* types have 'r 'a lifetime params if !added_lifetime { deflated_fields.unnamed.push(Field { vis: Visibility::Inherited, ty: parse_quote!(std::marker::PhantomData<&'r &'a ()>), attrs: Default::default(), colon_token: Default::default(), ident: Default::default(), }); } deflated_fields } fn impl_named_fields(mut fields: FieldsNamed) -> (Fields, Fields) { let mut deflated_fields = fields.clone(); let mut added_lifetime = false; // Drop whitespace fields from deflated fields // And add lifetimes to tokenref fields deflated_fields.named = deflated_fields .named .into_pairs() .filter(|pair| { let id = pair.value().ident.as_ref().unwrap().to_string(); !id.contains("whitespace") && id != "footer" && id != "header" && id != "leading_lines" && id != "lines_after_decorators" }) .map(|pair| { if is_builtin(pair.value()) { pair } else { let (deflated, lifetime) = make_into_deflated(pair); added_lifetime |= lifetime; deflated } }) .map(|pair| { let (mut val, punct) = pair.into_tuple(); val.attrs = val.attrs.into_iter().filter(is_not_intopy_attr).collect(); Pair::new(val, punct) }) .collect(); // Make sure all Deflated* types have 'r 'a lifetime params if !added_lifetime { deflated_fields.named.push(Field { attrs: Default::default(), vis: Visibility::Inherited, ident: Some(parse_quote!(_phantom)), colon_token: Default::default(), ty: parse_quote!(std::marker::PhantomData<&'r &'a ()>), }); } // Drop tokenref fields from inflated fields fields.named = fields .named .into_pairs() .filter(|pair| !is_token_ref(pair.value())) .collect(); (Fields::Named(fields), Fields::Named(deflated_fields)) } fn is_builtin(field: &Field) -> bool { get_pathseg(&field.ty) .map(|seg| { let segstr = seg.ident.to_string(); segstr == "str" || segstr == "bool" || segstr == "String" }) .unwrap_or_default() } fn is_token_ref(field: &Field) -> bool { if let Some(seg) = rightmost_path_segment(&field.ty) { return format!("{}", seg.ident) == "TokenRef"; } false } // foo::bar -> foo::Deflatedbar<'r, 'a> fn make_into_deflated(mut pair: Pair) -> (Pair, bool) { let mut added_lifetime = true; if let Some(seg) = rightmost_path_segment_mut(&mut pair.value_mut().ty) { let seg_name = seg.ident.to_string(); if seg_name != "TokenRef" { seg.ident = format_ident!("Deflated{}", seg_name); } match seg.arguments { PathArguments::None => { seg.arguments = PathArguments::AngleBracketed(parse_quote!(<'r, 'a>)); } PathArguments::AngleBracketed(AngleBracketedGenericArguments { ref mut args, .. }) => { args.insert(0, parse_quote!('r)); } _ => todo!(), } } else { added_lifetime = false; } (pair, added_lifetime) } // foo::bar::baz> -> baz> fn get_pathseg(ty: &Type) -> Option<&PathSegment> { match ty { Type::Path(TypePath { path, .. }) => path.segments.last(), _ => None, } } // foo::bar::baz> -> quux<'a> fn rightmost_path_segment(ty: &Type) -> Option<&PathSegment> { let mut candidate = get_pathseg(ty); loop { if let Some(pathseg) = candidate { if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = &pathseg.arguments { if let Some(GenericArgument::Type(t)) = args.last() { candidate = get_pathseg(t); continue; } } } break; } candidate } fn get_pathseg_mut(ty: &mut Type) -> Option<&mut PathSegment> { match ty { Type::Path(TypePath { path, .. }) => path.segments.last_mut(), _ => None, } } fn has_more_mut(candidate: &Option<&mut PathSegment>) -> bool { if let Some(PathArguments::AngleBracketed(AngleBracketedGenericArguments { ref args, .. })) = candidate.as_ref().map(|c| &c.arguments) { matches!(args.last(), Some(GenericArgument::Type(_))) } else { false } } fn rightmost_path_segment_mut(ty: &mut Type) -> Option<&mut PathSegment> { let mut candidate = get_pathseg_mut(ty); while has_more_mut(&candidate) { candidate = match candidate.unwrap().arguments { PathArguments::AngleBracketed(AngleBracketedGenericArguments { ref mut args, .. }) => { if let Some(GenericArgument::Type(t)) = args.last_mut() { get_pathseg_mut(t) } else { unreachable!(); } } _ => unreachable!(), }; } candidate } fn is_not_intopy_attr(attr: &Attribute) -> bool { let path = &attr.path; // support #[cfg_attr(feature="py", skip_py)] if path.is_ident("cfg_attr") { match attr.parse_meta() { Ok(Meta::List(MetaList { nested, .. })) => { for meta in nested { if let NestedMeta::Meta(Meta::Path(path)) = meta { return !is_intopy_attr_path(&path); } } } _ => return false, } } !is_intopy_attr_path(path) } fn is_intopy_attr_path(path: &Path) -> bool { path.is_ident("skip_py") || path.is_ident("no_py_default") } #[test] fn trybuild() { let t = trybuild::TestCases::new(); t.pass("tests/pass/*.rs"); } #[test] fn test_is_not_intopy_attr() { assert!(!is_not_intopy_attr(&parse_quote!(#[skip_py]))); assert!(!is_not_intopy_attr(&parse_quote!(#[no_py_default]))); assert!(!is_not_intopy_attr( &parse_quote!(#[cfg_attr(foo="bar",skip_py)]) )); assert!(!is_not_intopy_attr( &parse_quote!(#[cfg_attr(foo="bar",no_py_default)]) )); assert!(is_not_intopy_attr(&parse_quote!(#[skippy]))); assert!(is_not_intopy_attr( &parse_quote!(#[cfg_attr(foo="bar",skippy)]) )); } libcst_derive-0.1.0/src/inflate.rs000064400000000000000000000051520072674642500152520ustar 00000000000000// Copyright (c) Meta Platforms, Inc. and affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree use proc_macro::TokenStream; use quote::{format_ident, quote, quote_spanned}; use syn::{self, spanned::Spanned, Data, DataEnum, DeriveInput, Fields, FieldsUnnamed}; pub(crate) fn impl_inflate(ast: &DeriveInput) -> TokenStream { match &ast.data { Data::Enum(e) => impl_inflate_enum(ast, e), Data::Struct(s) => quote_spanned! { s.struct_token.span() => compile_error!("Struct type is not supported") } .into(), Data::Union(u) => quote_spanned! { u.union_token.span() => compile_error!("Union type is not supported") } .into(), } } fn impl_inflate_enum(ast: &DeriveInput, e: &DataEnum) -> TokenStream { let mut varnames = vec![]; for var in e.variants.iter() { match &var.fields { Fields::Named(n) => { return quote_spanned! { n.span() => compile_error!("Named enum fields not supported") } .into() } f @ Fields::Unit => { return quote_spanned! { f.span() => compile_error!("Empty enum variants not supported") } .into() } Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { if unnamed.len() > 1 { return quote_spanned! { unnamed.span() => compile_error!("Multiple unnamed fields not supported") } .into(); } varnames.push(&var.ident); } } } let ident = &ast.ident; let generics = &ast.generics; let ident_str = ident.to_string(); let inflated_ident = format_ident!( "{}", ident_str .strip_prefix("Deflated") .expect("Cannot implement Inflate on a non-Deflated item") ); let gen = quote! { impl#generics Inflate<'a> for #ident #generics { type Inflated = #inflated_ident <'a>; fn inflate(mut self, config: & crate::tokenizer::whitespace_parser::Config<'a>) -> std::result::Result { match self { #(Self::#varnames(x) => Ok(Self::Inflated::#varnames(x.inflate(config)?)),)* } } } }; gen.into() } libcst_derive-0.1.0/src/into_py.rs000064400000000000000000000151340072674642500153120ustar 00000000000000// Copyright (c) Meta Platforms, Inc. and affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree use proc_macro::TokenStream; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Type, TypePath, Visibility, }; pub(crate) fn impl_into_py(ast: &DeriveInput) -> TokenStream { match &ast.data { Data::Enum(e) => impl_into_py_enum(ast, e), Data::Struct(s) => impl_into_py_struct(ast, s), Data::Union(u) => quote_spanned! { u.union_token.span() => compile_error!("Union type is not supported") } .into(), } } fn impl_into_py_enum(ast: &DeriveInput, e: &DataEnum) -> TokenStream { let mut toks = vec![]; for var in e.variants.iter() { let varname = &var.ident; match &var.fields { Fields::Named(n) => { let mut fieldnames = vec![]; for field in n.named.iter() { if has_attr(&field.attrs, "skip_py") { continue; } fieldnames.push(field.ident.as_ref().unwrap()); } let kwargs_toks = fields_to_kwargs(&var.fields, true); toks.push(quote! { Self::#varname { #(#fieldnames,)* .. } => { let libcst = pyo3::types::PyModule::import(py, "libcst")?; let kwargs = #kwargs_toks ; Ok(libcst .getattr(stringify!(#varname)) .expect(stringify!(no #varname found in libcst)) .call((), Some(kwargs))? .into()) } }) } f @ Fields::Unit => { return quote_spanned! { f.span() => compile_error!("Empty enum variants not supported") } .into() } Fields::Unnamed(_) => { toks.push(quote! { Self::#varname(x, ..) => x.try_into_py(py), }); } } } let ident = &ast.ident; let generics = &ast.generics; let gen = quote! { use pyo3::types::IntoPyDict as _; #[automatically_derived] impl#generics crate::nodes::traits::py::TryIntoPy for #ident #generics { fn try_into_py(self, py: pyo3::Python) -> pyo3::PyResult { match self { #(#toks)* } } } }; gen.into() } fn impl_into_py_struct(ast: &DeriveInput, e: &DataStruct) -> TokenStream { let kwargs_toks = fields_to_kwargs(&e.fields, false); let ident = &ast.ident; let generics = &ast.generics; let gen = quote! { use pyo3::types::IntoPyDict as _; #[automatically_derived] impl#generics crate::nodes::traits::py::TryIntoPy for #ident #generics { fn try_into_py(self, py: pyo3::Python) -> pyo3::PyResult { let libcst = pyo3::types::PyModule::import(py, "libcst")?; let kwargs = #kwargs_toks ; Ok(libcst .getattr(stringify!(#ident)) .expect(stringify!(no #ident found in libcst)) .call((), Some(kwargs))? .into()) } } }; gen.into() } fn fields_to_kwargs(fields: &Fields, is_enum: bool) -> quote::__private::TokenStream { let mut empty_kwargs = false; let mut py_varnames = vec![]; let mut rust_varnames = vec![]; let mut optional_py_varnames = vec![]; let mut optional_rust_varnames = vec![]; match &fields { Fields::Named(FieldsNamed { named, .. }) => { for field in named.iter() { if has_attr(&field.attrs, "skip_py") { continue; } if let Some(ident) = field.ident.as_ref() { let include = if let Visibility::Public(_) = field.vis { true } else { is_enum }; if include { let pyname = format_ident!("{}", ident); let rustname = if is_enum { ident.to_token_stream() } else { quote! { self.#ident } }; if !has_attr(&field.attrs, "no_py_default") { if let Type::Path(TypePath { path, .. }) = &field.ty { if let Some(first) = path.segments.first() { if first.ident == "Option" { optional_py_varnames.push(pyname); optional_rust_varnames.push(rustname); continue; } } } } py_varnames.push(pyname); rust_varnames.push(rustname); } } } empty_kwargs = py_varnames.is_empty() && optional_py_varnames.is_empty() } Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { if unnamed.first().is_some() { py_varnames.push(format_ident!("value")); rust_varnames.push(quote! { self.0 }); } else { empty_kwargs = true; } } Fields::Unit => { empty_kwargs = true; } }; let kwargs_pairs = quote! { #(Some((stringify!(#py_varnames), #rust_varnames.try_into_py(py)?)),)* }; let optional_pairs = quote! { #(#optional_rust_varnames.map(|x| x.try_into_py(py)).transpose()?.map(|x| (stringify!(#optional_py_varnames), x)),)* }; if empty_kwargs { quote! { pyo3::types::PyDict::new(py) } } else { quote! { [ #kwargs_pairs #optional_pairs ] .iter() .filter(|x| x.is_some()) .map(|x| x.as_ref().unwrap()) .collect::>() .into_py_dict(py) } } } fn has_attr(attrs: &[Attribute], name: &'static str) -> bool { attrs.iter().any(|attr| attr.path.is_ident(name)) } libcst_derive-0.1.0/src/lib.rs000064400000000000000000000030340072674642500143730ustar 00000000000000// Copyright (c) Meta Platforms, Inc. and affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree mod inflate; use inflate::impl_inflate; mod parenthesized_node; use parenthesized_node::impl_parenthesized_node; mod codegen; use codegen::impl_codegen; mod into_py; use into_py::impl_into_py; mod cstnode; use cstnode::{impl_cst_node, CSTNodeParams}; use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; #[proc_macro_derive(Inflate)] pub fn inflate_derive(input: TokenStream) -> TokenStream { let ast = syn::parse(input).unwrap(); impl_inflate(&ast) } #[proc_macro_derive(ParenthesizedNode)] pub fn parenthesized_node_derive(input: TokenStream) -> TokenStream { impl_parenthesized_node(&syn::parse(input).unwrap(), false) } #[proc_macro_derive(ParenthesizedDeflatedNode)] pub fn parenthesized_deflated_node_derive(input: TokenStream) -> TokenStream { impl_parenthesized_node(&syn::parse(input).unwrap(), true) } #[proc_macro_derive(Codegen)] pub fn codegen_derive(input: TokenStream) -> TokenStream { impl_codegen(&syn::parse(input).unwrap()) } #[proc_macro_derive(TryIntoPy, attributes(skip_py, no_py_default))] pub fn into_py(input: TokenStream) -> TokenStream { impl_into_py(&syn::parse(input).unwrap()) } #[proc_macro_attribute] pub fn cst_node(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as CSTNodeParams); impl_cst_node(parse_macro_input!(input as DeriveInput), args) } libcst_derive-0.1.0/src/parenthesized_node.rs000064400000000000000000000101160072674642500174760ustar 00000000000000// Copyright (c) Meta Platforms, Inc. and affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree use proc_macro::TokenStream; use quote::{quote, quote_spanned}; use syn::{ parse_quote, spanned::Spanned, Data, DataEnum, DeriveInput, Fields, FieldsUnnamed, Ident, }; pub(crate) fn impl_parenthesized_node(ast: &DeriveInput, deflated: bool) -> TokenStream { match &ast.data { Data::Enum(e) => impl_enum(ast, e, deflated), Data::Struct(_) => impl_struct(ast, deflated), Data::Union(u) => quote_spanned! { u.union_token.span() => compile_error!("Union type is not supported") } .into(), } } fn idents(deflated: bool) -> (Ident, Ident, Ident) { let treyt: Ident = if deflated { parse_quote!(ParenthesizedDeflatedNode) } else { parse_quote!(ParenthesizedNode) }; let leftparen: Ident = if deflated { parse_quote!(DeflatedLeftParen) } else { parse_quote!(LeftParen) }; let rightparen: Ident = if deflated { parse_quote!(DeflatedRightParen) } else { parse_quote!(RightParen) }; (treyt, leftparen, rightparen) } fn impl_struct(ast: &DeriveInput, deflated: bool) -> TokenStream { let ident = &ast.ident; let generics = if deflated { parse_quote!(<'r, 'a>) } else { ast.generics.clone() }; let (treyt, leftparen, rightparen) = idents(deflated); let gen = quote! { impl#generics #treyt#generics for #ident #generics { fn lpar(&self) -> &Vec<#leftparen#generics> { &self.lpar } fn rpar(&self) -> &Vec<#rightparen#generics> { &self.rpar } fn with_parens(self, left: #leftparen#generics, right: #rightparen#generics) -> Self { let mut lpar = self.lpar; let mut rpar = self.rpar; lpar.insert(0, left); rpar.push(right); #[allow(clippy::needless_update)] Self { lpar, rpar, ..self } } } }; gen.into() } fn impl_enum(ast: &DeriveInput, e: &DataEnum, deflated: bool) -> TokenStream { let mut varnames = vec![]; for var in e.variants.iter() { match &var.fields { Fields::Named(n) => { return quote_spanned! { n.span() => compile_error!("Named enum fields not supported") } .into() } f @ Fields::Unit => { return quote_spanned! { f.span() => compile_error!("Empty enum variants not supported") } .into() } Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { if unnamed.len() > 1 { return quote_spanned! { unnamed.span() => compile_error!("Multiple unnamed fields not supported") } .into(); } varnames.push(&var.ident); } } } let ident = &ast.ident; let generics = if deflated { parse_quote!(<'r, 'a>) } else { ast.generics.clone() }; let (treyt, leftparen, rightparen) = idents(deflated); let gen = quote! { impl#generics #treyt#generics for #ident #generics { fn lpar(&self) -> &Vec<#leftparen#generics> { match self { #(Self::#varnames(x) => x.lpar(),)* } } fn rpar(&self) -> &Vec<#rightparen#generics> { match self { #(Self::#varnames(x) => x.rpar(),)* } } fn with_parens(self, left: #leftparen#generics, right: #rightparen#generics) -> Self { match self { #(Self::#varnames(x) => Self::#varnames(x.with_parens(left, right)),)* } } } }; gen.into() } libcst_derive-0.1.0/tests/pass/minimal_cst.rs000064400000000000000000000057150072674642500174550ustar 00000000000000// Copyright (c) Meta Platforms, Inc. and affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree use libcst_derive::{cst_node, Codegen}; pub enum Error {} type TokenRef<'r, 'a> = &'r &'a str; pub type Result = std::result::Result; pub struct Config<'a> { #[allow(dead_code)] foo: &'a str, } pub trait Inflate<'a> where Self: Sized, { type Inflated; fn inflate(self, config: &Config<'a>) -> Result; } impl<'a, T: Inflate<'a> + ?Sized> Inflate<'a> for Box { type Inflated = Box; fn inflate(self, config: &Config<'a>) -> Result { match (*self).inflate(config) { Ok(a) => Ok(Box::new(a)), Err(e) => Err(e), } } } pub struct CodegenState<'a> { #[allow(dead_code)] foo: &'a str, } pub trait Codegen<'a> { fn codegen(&self, state: &mut CodegenState<'a>); } #[derive(Debug, PartialEq, Eq, Clone)] pub struct WS<'a> { pub last_line: &'a str, } #[cst_node] pub struct Parameters<'a> { pub params: Vec>, pub foo: Param<'a>, } impl<'r, 'a> Inflate<'a> for DeflatedParameters<'r, 'a> { type Inflated = Parameters<'a>; fn inflate(self, config: &Config<'a>) -> Result { let params = vec![]; #[allow(clippy::blacklisted_name)] let foo = self.foo.inflate(config)?; Ok(Self::Inflated { params, foo }) } } #[cst_node] pub struct Param<'a> { pub star: Option<&'a str>, pub(crate) star_tok: Option>, } impl<'r, 'a> Inflate<'a> for DeflatedParam<'r, 'a> { type Inflated = Param<'a>; fn inflate(self, _config: &Config<'a>) -> Result { Ok(Self::Inflated { star: self.star }) } } impl<'a> Codegen<'a> for Param<'a> { fn codegen(&self, _state: &mut CodegenState<'a>) {} } #[cst_node] pub struct BitOr<'a> { pub whitespace_before: WS<'a>, pub whitespace_after: WS<'a>, pub(crate) tok: TokenRef<'a>, } #[cst_node] pub enum CompOp<'a> { LessThan { whitespace_before: WS<'a>, tok: TokenRef<'a>, }, GreaterThan { whitespace_after: WS<'a>, tok: TokenRef<'a>, }, } impl<'r, 'a> Inflate<'a> for DeflatedCompOp<'r, 'a> { type Inflated = CompOp<'a>; fn inflate(self, _config: &Config<'a>) -> Result { Ok(match self { Self::LessThan { tok: _, .. } => Self::Inflated::LessThan { whitespace_before: WS { last_line: "yo" }, }, Self::GreaterThan { tok: _, .. } => Self::Inflated::GreaterThan { whitespace_after: WS { last_line: "" }, }, }) } } impl<'a> Codegen<'a> for CompOp<'a> { fn codegen(&self, _state: &mut CodegenState<'a>) {} } #[cst_node(Codegen)] enum Expr<'a> { #[allow(dead_code)] One(Box>), #[allow(dead_code)] Two(CompOp<'a>), } fn main() {} libcst_derive-0.1.0/tests/pass/simple.rs000064400000000000000000000016130072674642500164400ustar 00000000000000// Copyright (c) Meta Platforms, Inc. and affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree use libcst_derive::cst_node; #[derive(Debug, PartialEq, Eq, Clone)] pub struct WS<'a>(&'a str); type TokenRef<'r, 'a> = &'r &'a str; #[cst_node] pub enum Foo<'a> { One(One<'a>), Two(Box>), } #[cst_node] pub struct One<'a> { pub two: Box>, pub header: WS<'a>, pub(crate) newline_tok: TokenRef<'a>, } #[cst_node] pub struct Two<'a> { pub whitespace_before: WS<'a>, pub(crate) tok: TokenRef<'a>, } #[cst_node] struct Thin<'a> { pub whitespace: WS<'a>, } #[cst_node] struct Value<'a> { pub value: &'a str, } #[cst_node] struct Empty {} #[cst_node] enum Smol<'a> { #[allow(dead_code)] Thin(Thin<'a>), #[allow(dead_code)] Empty(Empty), } fn main() {}