mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Split derive-impl and derive
This commit is contained in:
15 files changed
+148
-97
No files matched your search
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "rustpython-derive-impl"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rustpython-compiler-core = { path = "../compiler/core", version = "0.1.1" }
|
||||
rustpython-doc = { git = "https://github.com/RustPython/__doc__", branch = "main" }
|
||||
|
||||
indexmap = "1.8.1"
|
||||
itertools = "0.10.3"
|
||||
maplit = "1.0.2"
|
||||
once_cell = "1.10.0"
|
||||
proc-macro2 = "1.0.37"
|
||||
quote = "1.0.18"
|
||||
syn = { version = "1.0.91", features = ["full", "extra-traits"] }
|
||||
syn-ext = { version = "0.4.0", features = ["full"] }
|
||||
textwrap = { version = "0.15.0", default-features = false }
|
||||
@@ -0,0 +1,402 @@
|
||||
//! Parsing and processing for this form:
|
||||
//! ```ignore
|
||||
//! py_compile!(
|
||||
//! // either:
|
||||
//! source = "python_source_code",
|
||||
//! // or
|
||||
//! file = "file/path/relative/to/$CARGO_MANIFEST_DIR",
|
||||
//!
|
||||
//! // the mode to compile the code in
|
||||
//! mode = "exec", // or "eval" or "single"
|
||||
//! // the path put into the CodeObject, defaults to "frozen"
|
||||
//! module_name = "frozen",
|
||||
//! )
|
||||
//! ```
|
||||
|
||||
use crate::{extract_spans, Diagnostic};
|
||||
use once_cell::sync::Lazy;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use rustpython_compiler_core::{CodeObject, FrozenModule, Mode};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use syn::{
|
||||
self,
|
||||
parse::{Parse, ParseStream, Result as ParseResult},
|
||||
parse2,
|
||||
spanned::Spanned,
|
||||
Lit, LitByteStr, LitStr, Macro, Meta, MetaNameValue, Token,
|
||||
};
|
||||
|
||||
static CARGO_MANIFEST_DIR: Lazy<PathBuf> = Lazy::new(|| {
|
||||
PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not present"))
|
||||
});
|
||||
|
||||
enum CompilationSourceKind {
|
||||
/// Source is a File (Path)
|
||||
File(PathBuf),
|
||||
/// Direct Raw sourcecode
|
||||
SourceCode(String),
|
||||
/// Source is a directory
|
||||
Dir(PathBuf),
|
||||
}
|
||||
|
||||
struct CompilationSource {
|
||||
kind: CompilationSourceKind,
|
||||
span: (Span, Span),
|
||||
}
|
||||
|
||||
pub trait Compiler {
|
||||
fn compile(
|
||||
&self,
|
||||
source: &str,
|
||||
mode: Mode,
|
||||
module_name: String,
|
||||
) -> Result<CodeObject, Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
impl CompilationSource {
|
||||
fn compile_string<D: std::fmt::Display, F: FnOnce() -> D>(
|
||||
&self,
|
||||
source: &str,
|
||||
mode: Mode,
|
||||
module_name: String,
|
||||
compiler: &dyn Compiler,
|
||||
origin: F,
|
||||
) -> Result<CodeObject, Diagnostic> {
|
||||
compiler.compile(source, mode, module_name).map_err(|err| {
|
||||
Diagnostic::spans_error(
|
||||
self.span,
|
||||
format!("Python compile error from {}: {}", origin(), err),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn compile(
|
||||
&self,
|
||||
mode: Mode,
|
||||
module_name: String,
|
||||
compiler: &dyn Compiler,
|
||||
) -> Result<HashMap<String, FrozenModule>, Diagnostic> {
|
||||
match &self.kind {
|
||||
CompilationSourceKind::Dir(rel_path) => self.compile_dir(
|
||||
&CARGO_MANIFEST_DIR.join(rel_path),
|
||||
String::new(),
|
||||
mode,
|
||||
compiler,
|
||||
),
|
||||
_ => Ok(hashmap! {
|
||||
module_name.clone() => FrozenModule {
|
||||
code: self.compile_single(mode, module_name, compiler)?,
|
||||
package: false,
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_single(
|
||||
&self,
|
||||
mode: Mode,
|
||||
module_name: String,
|
||||
compiler: &dyn Compiler,
|
||||
) -> Result<CodeObject, Diagnostic> {
|
||||
match &self.kind {
|
||||
CompilationSourceKind::File(rel_path) => {
|
||||
let path = CARGO_MANIFEST_DIR.join(rel_path);
|
||||
let source = fs::read_to_string(&path).map_err(|err| {
|
||||
Diagnostic::spans_error(
|
||||
self.span,
|
||||
format!("Error reading file {:?}: {}", path, err),
|
||||
)
|
||||
})?;
|
||||
self.compile_string(&source, mode, module_name, compiler, || rel_path.display())
|
||||
}
|
||||
CompilationSourceKind::SourceCode(code) => {
|
||||
self.compile_string(&textwrap::dedent(code), mode, module_name, compiler, || {
|
||||
"string literal"
|
||||
})
|
||||
}
|
||||
CompilationSourceKind::Dir(_) => {
|
||||
unreachable!("Can't use compile_single with directory source")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_dir(
|
||||
&self,
|
||||
path: &Path,
|
||||
parent: String,
|
||||
mode: Mode,
|
||||
compiler: &dyn Compiler,
|
||||
) -> Result<HashMap<String, FrozenModule>, Diagnostic> {
|
||||
let mut code_map = HashMap::new();
|
||||
let paths = fs::read_dir(path)
|
||||
.or_else(|e| {
|
||||
if cfg!(windows) {
|
||||
if let Ok(real_path) = fs::read_to_string(path.canonicalize().unwrap()) {
|
||||
return fs::read_dir(real_path.trim());
|
||||
}
|
||||
}
|
||||
Err(e)
|
||||
})
|
||||
.map_err(|err| {
|
||||
Diagnostic::spans_error(self.span, format!("Error listing dir {:?}: {}", path, err))
|
||||
})?;
|
||||
for path in paths {
|
||||
let path = path.map_err(|err| {
|
||||
Diagnostic::spans_error(self.span, format!("Failed to list file: {}", err))
|
||||
})?;
|
||||
let path = path.path();
|
||||
let file_name = path.file_name().unwrap().to_str().ok_or_else(|| {
|
||||
Diagnostic::spans_error(self.span, format!("Invalid UTF-8 in file name {:?}", path))
|
||||
})?;
|
||||
if path.is_dir() {
|
||||
code_map.extend(self.compile_dir(
|
||||
&path,
|
||||
if parent.is_empty() {
|
||||
file_name.to_string()
|
||||
} else {
|
||||
format!("{}.{}", parent, file_name)
|
||||
},
|
||||
mode,
|
||||
compiler,
|
||||
)?);
|
||||
} else if file_name.ends_with(".py") {
|
||||
let stem = path.file_stem().unwrap().to_str().unwrap();
|
||||
let is_init = stem == "__init__";
|
||||
let module_name = if is_init {
|
||||
parent.clone()
|
||||
} else if parent.is_empty() {
|
||||
stem.to_owned()
|
||||
} else {
|
||||
format!("{}.{}", parent, stem)
|
||||
};
|
||||
|
||||
let compile_path = |src_path: &Path| {
|
||||
let source = fs::read_to_string(src_path).map_err(|err| {
|
||||
Diagnostic::spans_error(
|
||||
self.span,
|
||||
format!("Error reading file {:?}: {}", path, err),
|
||||
)
|
||||
})?;
|
||||
self.compile_string(&source, mode, module_name.clone(), compiler, || {
|
||||
path.strip_prefix(&*CARGO_MANIFEST_DIR)
|
||||
.ok()
|
||||
.unwrap_or(&path)
|
||||
.display()
|
||||
})
|
||||
};
|
||||
let code = compile_path(&path).or_else(|e| {
|
||||
if cfg!(windows) {
|
||||
if let Ok(real_path) = fs::read_to_string(path.canonicalize().unwrap()) {
|
||||
let joined = path.parent().unwrap().join(real_path.trim());
|
||||
if joined.exists() {
|
||||
return compile_path(&joined);
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e)
|
||||
});
|
||||
|
||||
let code = match code {
|
||||
Ok(code) => code,
|
||||
Err(_)
|
||||
if stem.starts_with("badsyntax_")
|
||||
| parent.ends_with(".encoded_modules") =>
|
||||
{
|
||||
// TODO: handle with macro arg rather than hard-coded path
|
||||
continue;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
code_map.insert(
|
||||
module_name,
|
||||
FrozenModule {
|
||||
code,
|
||||
package: is_init,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(code_map)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is essentially just a comma-separated list of Meta nodes, aka the inside of a MetaList.
|
||||
struct PyCompileInput {
|
||||
span: Span,
|
||||
metas: Vec<Meta>,
|
||||
}
|
||||
|
||||
impl PyCompileInput {
|
||||
fn parse(&self, allow_dir: bool) -> Result<PyCompileArgs, Diagnostic> {
|
||||
let mut module_name = None;
|
||||
let mut mode = None;
|
||||
let mut source: Option<CompilationSource> = None;
|
||||
let mut crate_name = None;
|
||||
|
||||
fn assert_source_empty(source: &Option<CompilationSource>) -> Result<(), Diagnostic> {
|
||||
if let Some(source) = source {
|
||||
Err(Diagnostic::spans_error(
|
||||
source.span,
|
||||
"Cannot have more than one source",
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
for meta in &self.metas {
|
||||
if let Meta::NameValue(name_value) = meta {
|
||||
let ident = match name_value.path.get_ident() {
|
||||
Some(ident) => ident,
|
||||
None => continue,
|
||||
};
|
||||
let check_str = || match &name_value.lit {
|
||||
Lit::Str(s) => Ok(s),
|
||||
_ => Err(err_span!(name_value.lit, "{ident} must be a string")),
|
||||
};
|
||||
if ident == "mode" {
|
||||
let s = check_str()?;
|
||||
match s.value().parse() {
|
||||
Ok(mode_val) => mode = Some(mode_val),
|
||||
Err(e) => bail_span!(s, "{}", e),
|
||||
}
|
||||
} else if ident == "module_name" {
|
||||
module_name = Some(check_str()?.value())
|
||||
} else if ident == "source" {
|
||||
assert_source_empty(&source)?;
|
||||
let code = check_str()?.value();
|
||||
source = Some(CompilationSource {
|
||||
kind: CompilationSourceKind::SourceCode(code),
|
||||
span: extract_spans(&name_value).unwrap(),
|
||||
});
|
||||
} else if ident == "file" {
|
||||
assert_source_empty(&source)?;
|
||||
let path = check_str()?.value().into();
|
||||
source = Some(CompilationSource {
|
||||
kind: CompilationSourceKind::File(path),
|
||||
span: extract_spans(&name_value).unwrap(),
|
||||
});
|
||||
} else if ident == "dir" {
|
||||
if !allow_dir {
|
||||
bail_span!(ident, "py_compile doesn't accept dir")
|
||||
}
|
||||
|
||||
assert_source_empty(&source)?;
|
||||
let path = check_str()?.value().into();
|
||||
source = Some(CompilationSource {
|
||||
kind: CompilationSourceKind::Dir(path),
|
||||
span: extract_spans(&name_value).unwrap(),
|
||||
});
|
||||
} else if ident == "crate_name" {
|
||||
let name = check_str()?.parse()?;
|
||||
crate_name = Some(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let source = source.ok_or_else(|| {
|
||||
syn::Error::new(
|
||||
self.span,
|
||||
"Must have either file or source in py_compile!()/py_freeze!()",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(PyCompileArgs {
|
||||
source,
|
||||
mode: mode.unwrap_or(Mode::Exec),
|
||||
module_name: module_name.unwrap_or_else(|| "frozen".to_owned()),
|
||||
crate_name: crate_name.unwrap_or_else(|| syn::parse_quote!(::rustpython_vm::bytecode)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_meta(input: ParseStream) -> ParseResult<Meta> {
|
||||
let path = input.call(syn::Path::parse_mod_style)?;
|
||||
let eq_token: Token![=] = input.parse()?;
|
||||
let span = input.span();
|
||||
if input.peek(LitStr) {
|
||||
Ok(Meta::NameValue(MetaNameValue {
|
||||
path,
|
||||
eq_token,
|
||||
lit: Lit::Str(input.parse()?),
|
||||
}))
|
||||
} else if let Ok(mac) = input.parse::<Macro>() {
|
||||
Ok(Meta::NameValue(MetaNameValue {
|
||||
path,
|
||||
eq_token,
|
||||
lit: Lit::Str(LitStr::new(&mac.tokens.to_string(), mac.span())),
|
||||
}))
|
||||
} else {
|
||||
Err(syn::Error::new(span, "Expected string or stringify macro"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for PyCompileInput {
|
||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||
let span = input.cursor().span();
|
||||
let metas = input
|
||||
.parse_terminated::<Meta, Token![,]>(parse_meta)?
|
||||
.into_iter()
|
||||
.collect();
|
||||
Ok(PyCompileInput { span, metas })
|
||||
}
|
||||
}
|
||||
|
||||
struct PyCompileArgs {
|
||||
source: CompilationSource,
|
||||
mode: Mode,
|
||||
module_name: String,
|
||||
crate_name: syn::Path,
|
||||
}
|
||||
|
||||
pub fn impl_py_compile(
|
||||
input: TokenStream,
|
||||
compiler: &dyn Compiler,
|
||||
) -> Result<TokenStream, Diagnostic> {
|
||||
let input: PyCompileInput = parse2(input)?;
|
||||
let args = input.parse(false)?;
|
||||
|
||||
let crate_name = args.crate_name;
|
||||
let code = args
|
||||
.source
|
||||
.compile_single(args.mode, args.module_name, compiler)?;
|
||||
|
||||
let bytes = code.to_bytes();
|
||||
let bytes = LitByteStr::new(&bytes, Span::call_site());
|
||||
|
||||
let output = quote! {
|
||||
#crate_name::CodeObject::from_bytes(#bytes)
|
||||
.expect("Deserializing CodeObject failed")
|
||||
};
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub fn impl_py_freeze(
|
||||
input: TokenStream,
|
||||
compiler: &dyn Compiler,
|
||||
) -> Result<TokenStream, Diagnostic> {
|
||||
let input: PyCompileInput = parse2(input)?;
|
||||
let args = input.parse(true)?;
|
||||
|
||||
let crate_name = args.crate_name;
|
||||
let code_map = args.source.compile(args.mode, args.module_name, compiler)?;
|
||||
|
||||
let data =
|
||||
rustpython_compiler_core::frozen_lib::encode_lib(code_map.iter().map(|(k, v)| (&**k, v)));
|
||||
let bytes = LitByteStr::new(&data, Span::call_site());
|
||||
|
||||
let output = quote! {
|
||||
#crate_name::frozen_lib::decode_lib(#bytes)
|
||||
};
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// Taken from https://github.com/rustwasm/wasm-bindgen/blob/master/crates/backend/src/error.rs
|
||||
//
|
||||
// Copyright (c) 2014 Alex Crichton
|
||||
//
|
||||
// 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.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use proc_macro2::*;
|
||||
use quote::{ToTokens, TokenStreamExt};
|
||||
use syn::parse::Error;
|
||||
|
||||
macro_rules! err_span {
|
||||
($span:expr, $($msg:tt)*) => (
|
||||
syn::Error::new_spanned(&$span, format_args!($($msg)*))
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! bail_span {
|
||||
($($t:tt)*) => (
|
||||
return Err(err_span!($($t)*).into())
|
||||
)
|
||||
}
|
||||
|
||||
// macro_rules! push_err_span {
|
||||
// ($diagnostics:expr, $($t:tt)*) => {
|
||||
// $diagnostics.push(err_span!($($t)*))
|
||||
// };
|
||||
// }
|
||||
|
||||
// macro_rules! push_diag_result {
|
||||
// ($diags:expr, $x:expr $(,)?) => {
|
||||
// if let Err(e) = $x {
|
||||
// $diags.push(e);
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Diagnostic {
|
||||
inner: Repr,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Repr {
|
||||
Single {
|
||||
text: String,
|
||||
span: Option<(Span, Span)>,
|
||||
},
|
||||
SynError(Error),
|
||||
Multi {
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
pub fn error<T: Into<String>>(text: T) -> Diagnostic {
|
||||
Diagnostic {
|
||||
inner: Repr::Single {
|
||||
text: text.into(),
|
||||
span: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn spans_error<T: Into<String>>(spans: (Span, Span), text: T) -> Diagnostic {
|
||||
Diagnostic {
|
||||
inner: Repr::Single {
|
||||
text: text.into(),
|
||||
span: Some(spans),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_vec(diagnostics: Vec<Diagnostic>) -> Result<(), Diagnostic> {
|
||||
if diagnostics.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Diagnostic {
|
||||
inner: Repr::Multi { diagnostics },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn panic(&self) -> ! {
|
||||
match &self.inner {
|
||||
Repr::Single { text, .. } => panic!("{}", text),
|
||||
Repr::SynError(error) => panic!("{}", error),
|
||||
Repr::Multi { diagnostics } => diagnostics[0].panic(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for Diagnostic {
|
||||
fn from(err: Error) -> Diagnostic {
|
||||
Diagnostic {
|
||||
inner: Repr::SynError(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_spans(node: &dyn ToTokens) -> Option<(Span, Span)> {
|
||||
let mut t = TokenStream::new();
|
||||
node.to_tokens(&mut t);
|
||||
let mut tokens = t.into_iter();
|
||||
let start = tokens.next().map(|t| t.span());
|
||||
let end = tokens.last().map(|t| t.span());
|
||||
start.map(|start| (start, end.unwrap_or(start)))
|
||||
}
|
||||
|
||||
impl ToTokens for Diagnostic {
|
||||
fn to_tokens(&self, dst: &mut TokenStream) {
|
||||
match &self.inner {
|
||||
Repr::Single { text, span } => {
|
||||
let cs2 = (Span::call_site(), Span::call_site());
|
||||
let (start, end) = span.unwrap_or(cs2);
|
||||
dst.append(Ident::new("compile_error", start));
|
||||
dst.append(Punct::new('!', Spacing::Alone));
|
||||
let mut message = TokenStream::new();
|
||||
message.append(Literal::string(text));
|
||||
let mut group = Group::new(Delimiter::Brace, message);
|
||||
group.set_span(end);
|
||||
dst.append(group);
|
||||
}
|
||||
Repr::Multi { diagnostics } => {
|
||||
for diagnostic in diagnostics {
|
||||
diagnostic.to_tokens(dst);
|
||||
}
|
||||
}
|
||||
Repr::SynError(err) => {
|
||||
err.to_compile_error().to_tokens(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
parse_quote, Attribute, Data, DeriveInput, Expr, Field, Ident, Lit, Meta, NestedMeta, Result,
|
||||
};
|
||||
|
||||
/// The kind of the python parameter, this corresponds to the value of Parameter.kind
|
||||
/// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind)
|
||||
enum ParameterKind {
|
||||
PositionalOnly,
|
||||
PositionalOrKeyword,
|
||||
KeywordOnly,
|
||||
Flatten,
|
||||
}
|
||||
|
||||
impl ParameterKind {
|
||||
fn from_ident(ident: &Ident) -> Option<ParameterKind> {
|
||||
match ident.to_string().as_str() {
|
||||
"positional" => Some(ParameterKind::PositionalOnly),
|
||||
"any" => Some(ParameterKind::PositionalOrKeyword),
|
||||
"named" => Some(ParameterKind::KeywordOnly),
|
||||
"flatten" => Some(ParameterKind::Flatten),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ArgAttribute {
|
||||
name: Option<String>,
|
||||
kind: ParameterKind,
|
||||
default: Option<DefaultValue>,
|
||||
}
|
||||
// None == quote!(Default::default())
|
||||
type DefaultValue = Option<Expr>;
|
||||
|
||||
impl ArgAttribute {
|
||||
fn from_attribute(attr: &Attribute) -> Option<Result<ArgAttribute>> {
|
||||
if !attr.path.is_ident("pyarg") {
|
||||
return None;
|
||||
}
|
||||
let inner = move || {
|
||||
let Meta::List(list) = attr.parse_meta()? else {
|
||||
bail_span!(attr, "pyarg must be a list, like #[pyarg(...)]")
|
||||
};
|
||||
let mut iter = list.nested.iter();
|
||||
let first_arg = iter.next().ok_or_else(|| {
|
||||
err_span!(list, "There must be at least one argument to #[pyarg()]")
|
||||
})?;
|
||||
let kind = match first_arg {
|
||||
NestedMeta::Meta(Meta::Path(path)) => {
|
||||
path.get_ident().and_then(ParameterKind::from_ident)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let kind = kind.ok_or_else(|| {
|
||||
err_span!(
|
||||
first_arg,
|
||||
"The first argument to #[pyarg()] must be the parameter type, either \
|
||||
'positional', 'any', 'named', or 'flatten'."
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut attribute = ArgAttribute {
|
||||
name: None,
|
||||
kind,
|
||||
default: None,
|
||||
};
|
||||
|
||||
for arg in iter {
|
||||
attribute.parse_argument(arg)?;
|
||||
}
|
||||
|
||||
Ok(attribute)
|
||||
};
|
||||
Some(inner())
|
||||
}
|
||||
|
||||
fn parse_argument(&mut self, arg: &NestedMeta) -> Result<()> {
|
||||
if let ParameterKind::Flatten = self.kind {
|
||||
bail_span!(arg, "can't put additional arguments on a flatten arg")
|
||||
}
|
||||
match arg {
|
||||
NestedMeta::Meta(Meta::Path(path)) => {
|
||||
if path.is_ident("default") || path.is_ident("optional") {
|
||||
if self.default.is_none() {
|
||||
self.default = Some(None);
|
||||
}
|
||||
} else {
|
||||
bail_span!(path, "Unrecognised pyarg attribute");
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(name_value)) => {
|
||||
if name_value.path.is_ident("default") {
|
||||
if matches!(self.default, Some(Some(_))) {
|
||||
bail_span!(name_value, "Default already set");
|
||||
}
|
||||
|
||||
match name_value.lit {
|
||||
Lit::Str(ref val) => self.default = Some(Some(val.parse()?)),
|
||||
_ => bail_span!(name_value, "Expected string value for default argument"),
|
||||
}
|
||||
} else if name_value.path.is_ident("name") {
|
||||
if self.name.is_some() {
|
||||
bail_span!(name_value, "already have a name")
|
||||
}
|
||||
|
||||
match &name_value.lit {
|
||||
Lit::Str(val) => self.name = Some(val.value()),
|
||||
_ => bail_span!(name_value, "Expected string value for name argument"),
|
||||
}
|
||||
} else {
|
||||
bail_span!(name_value, "Unrecognised pyarg attribute");
|
||||
}
|
||||
}
|
||||
_ => bail_span!(arg, "Unrecognised pyarg attribute"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_field((i, field): (usize, &Field)) -> Result<TokenStream> {
|
||||
let mut pyarg_attrs = field
|
||||
.attrs
|
||||
.iter()
|
||||
.filter_map(ArgAttribute::from_attribute)
|
||||
.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||
let attr = if pyarg_attrs.is_empty() {
|
||||
ArgAttribute {
|
||||
name: None,
|
||||
kind: ParameterKind::PositionalOrKeyword,
|
||||
default: None,
|
||||
}
|
||||
} else if pyarg_attrs.len() == 1 {
|
||||
pyarg_attrs.remove(0)
|
||||
} else {
|
||||
bail_span!(field, "Multiple pyarg attributes on field");
|
||||
};
|
||||
|
||||
let name = field.ident.as_ref();
|
||||
let namestring = name.map(Ident::to_string);
|
||||
if matches!(&namestring, Some(s) if s.starts_with("_phantom")) {
|
||||
return Ok(quote! {
|
||||
#name: ::std::marker::PhantomData,
|
||||
});
|
||||
}
|
||||
let fieldname = match name {
|
||||
Some(id) => id.to_token_stream(),
|
||||
None => syn::Index::from(i).into_token_stream(),
|
||||
};
|
||||
if let ParameterKind::Flatten = attr.kind {
|
||||
return Ok(quote! {
|
||||
#fieldname: ::rustpython_vm::function::FromArgs::from_args(vm, args)?,
|
||||
});
|
||||
}
|
||||
let pyname = attr
|
||||
.name
|
||||
.or(namestring)
|
||||
.ok_or_else(|| err_span!(field, "field in tuple struct must have name attribute"))?;
|
||||
let middle = quote! {
|
||||
.map(|x| ::rustpython_vm::convert::TryFromObject::try_from_object(vm, x)).transpose()?
|
||||
};
|
||||
let ending = if let Some(default) = attr.default {
|
||||
let default = default.unwrap_or_else(|| parse_quote!(::std::default::Default::default()));
|
||||
quote! {
|
||||
.map(::rustpython_vm::function::FromArgOptional::from_inner)
|
||||
.unwrap_or_else(|| #default)
|
||||
}
|
||||
} else {
|
||||
let err = match attr.kind {
|
||||
ParameterKind::PositionalOnly | ParameterKind::PositionalOrKeyword => quote! {
|
||||
::rustpython_vm::function::ArgumentError::TooFewArgs
|
||||
},
|
||||
ParameterKind::KeywordOnly => quote! {
|
||||
::rustpython_vm::function::ArgumentError::RequiredKeywordArgument(#pyname.to_owned())
|
||||
},
|
||||
ParameterKind::Flatten => unreachable!(),
|
||||
};
|
||||
quote! {
|
||||
.ok_or_else(|| #err)?
|
||||
}
|
||||
};
|
||||
|
||||
let file_output = match attr.kind {
|
||||
ParameterKind::PositionalOnly => {
|
||||
quote! {
|
||||
#fieldname: args.take_positional()#middle #ending,
|
||||
}
|
||||
}
|
||||
ParameterKind::PositionalOrKeyword => {
|
||||
quote! {
|
||||
#fieldname: args.take_positional_keyword(#pyname)#middle #ending,
|
||||
}
|
||||
}
|
||||
ParameterKind::KeywordOnly => {
|
||||
quote! {
|
||||
#fieldname: args.take_keyword(#pyname)#middle #ending,
|
||||
}
|
||||
}
|
||||
ParameterKind::Flatten => unreachable!(),
|
||||
};
|
||||
Ok(file_output)
|
||||
}
|
||||
|
||||
pub fn impl_from_args(input: DeriveInput) -> Result<TokenStream> {
|
||||
let fields = match input.data {
|
||||
Data::Struct(syn::DataStruct { fields, .. }) => fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(generate_field)
|
||||
.collect::<Result<TokenStream>>()?,
|
||||
_ => bail_span!(input, "FromArgs input must be a struct"),
|
||||
};
|
||||
|
||||
let name = input.ident;
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let output = quote! {
|
||||
impl #impl_generics ::rustpython_vm::function::FromArgs for #name #ty_generics #where_clause {
|
||||
fn from_args(
|
||||
vm: &::rustpython_vm::VirtualMachine,
|
||||
args: &mut ::rustpython_vm::function::FuncArgs
|
||||
) -> ::std::result::Result<Self, ::rustpython_vm::function::ArgumentError> {
|
||||
Ok(#name { #fields })
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(output)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#![recursion_limit = "128"]
|
||||
#![doc(html_logo_url = "https://raw.githubusercontent.com/RustPython/RustPython/main/logo.png")]
|
||||
#![doc(html_root_url = "https://docs.rs/rustpython-derive/")]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
#[macro_use]
|
||||
extern crate maplit;
|
||||
|
||||
#[macro_use]
|
||||
mod error;
|
||||
#[macro_use]
|
||||
mod util;
|
||||
|
||||
mod compile_bytecode;
|
||||
mod from_args;
|
||||
mod pyclass;
|
||||
mod pymodule;
|
||||
mod pypayload;
|
||||
mod pystructseq;
|
||||
|
||||
use error::{extract_spans, Diagnostic};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use rustpython_doc as doc;
|
||||
use syn::{AttributeArgs, DeriveInput, Item};
|
||||
|
||||
pub use compile_bytecode::Compiler;
|
||||
|
||||
fn result_to_tokens(result: Result<TokenStream, impl Into<Diagnostic>>) -> TokenStream {
|
||||
result
|
||||
.map_err(|e| e.into())
|
||||
.unwrap_or_else(ToTokens::into_token_stream)
|
||||
}
|
||||
|
||||
pub fn derive_from_args(input: DeriveInput) -> TokenStream {
|
||||
result_to_tokens(from_args::impl_from_args(input))
|
||||
}
|
||||
|
||||
pub fn pyclass(attr: AttributeArgs, item: Item) -> TokenStream {
|
||||
if matches!(item, syn::Item::Impl(_) | syn::Item::Trait(_)) {
|
||||
result_to_tokens(pyclass::impl_pyimpl(attr, item))
|
||||
} else {
|
||||
result_to_tokens(pyclass::impl_pyclass(attr, item))
|
||||
}
|
||||
}
|
||||
|
||||
pub use pyclass::PyExceptionDef;
|
||||
pub fn define_exception(exc_def: PyExceptionDef) -> TokenStream {
|
||||
result_to_tokens(pyclass::impl_define_exception(exc_def))
|
||||
}
|
||||
|
||||
pub fn pyexception(attr: AttributeArgs, item: Item) -> TokenStream {
|
||||
result_to_tokens(pyclass::impl_pyexception(attr, item))
|
||||
}
|
||||
|
||||
pub fn pymodule(attr: AttributeArgs, item: Item) -> TokenStream {
|
||||
result_to_tokens(pymodule::impl_pymodule(attr, item))
|
||||
}
|
||||
|
||||
pub fn pystruct_sequence(input: DeriveInput) -> TokenStream {
|
||||
result_to_tokens(pystructseq::impl_pystruct_sequence(input))
|
||||
}
|
||||
|
||||
pub fn pystruct_sequence_try_from_object(input: DeriveInput) -> TokenStream {
|
||||
result_to_tokens(pystructseq::impl_pystruct_sequence_try_from_object(input))
|
||||
}
|
||||
|
||||
pub fn py_compile(input: TokenStream, compiler: &dyn Compiler) -> TokenStream {
|
||||
result_to_tokens(compile_bytecode::impl_py_compile(input, compiler))
|
||||
}
|
||||
|
||||
pub fn py_freeze(input: TokenStream, compiler: &dyn Compiler) -> TokenStream {
|
||||
result_to_tokens(compile_bytecode::impl_py_freeze(input, compiler))
|
||||
}
|
||||
|
||||
pub fn pypayload(input: DeriveInput) -> TokenStream {
|
||||
result_to_tokens(pypayload::impl_pypayload(input))
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,631 @@
|
||||
use crate::error::Diagnostic;
|
||||
use crate::util::{
|
||||
iter_use_idents, pyclass_ident_and_attrs, text_signature, AttrItemMeta, AttributeExt,
|
||||
ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ItemMeta, ItemNursery, SimpleItemMeta,
|
||||
ALL_ALLOWED_NAMES,
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use std::{collections::HashSet, str::FromStr};
|
||||
use syn::{parse_quote, spanned::Spanned, Attribute, AttributeArgs, Ident, Item, Result};
|
||||
use syn_ext::ext::*;
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
enum AttrName {
|
||||
Function,
|
||||
Attr,
|
||||
Class,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AttrName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
Self::Function => "pyfunction",
|
||||
Self::Attr => "pyattr",
|
||||
Self::Class => "pyclass",
|
||||
};
|
||||
s.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AttrName {
|
||||
type Err = String;
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"pyfunction" => Self::Function,
|
||||
"pyattr" => Self::Attr,
|
||||
"pyclass" => Self::Class,
|
||||
s => {
|
||||
return Err(s.to_owned());
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ModuleContext {
|
||||
name: String,
|
||||
module_extend_items: ItemNursery,
|
||||
errors: Vec<syn::Error>,
|
||||
}
|
||||
|
||||
pub fn impl_pymodule(attr: AttributeArgs, module_item: Item) -> Result<TokenStream> {
|
||||
let (doc, mut module_item) = match module_item {
|
||||
Item::Mod(m) => (m.attrs.doc(), m),
|
||||
other => bail_span!(other, "#[pymodule] can only be on a full module"),
|
||||
};
|
||||
let fake_ident = Ident::new("pymodule", module_item.span());
|
||||
let module_meta =
|
||||
SimpleItemMeta::from_nested(module_item.ident.clone(), fake_ident, attr.into_iter())?;
|
||||
|
||||
// generation resources
|
||||
let mut context = ModuleContext {
|
||||
name: module_meta.simple_name()?,
|
||||
..Default::default()
|
||||
};
|
||||
let items = module_item.items_mut().ok_or_else(|| {
|
||||
module_meta.new_meta_error("requires actual module, not a module declaration")
|
||||
})?;
|
||||
|
||||
// collect to context
|
||||
for item in items.iter_mut() {
|
||||
if matches!(item, Item::Impl(_) | Item::Trait(_)) {
|
||||
// #[pyclass] implementations
|
||||
continue;
|
||||
}
|
||||
let r = item.try_split_attr_mut(|attrs, item| {
|
||||
let (pyitems, cfgs) = attrs_to_module_items(attrs, module_item_new)?;
|
||||
for pyitem in pyitems.iter().rev() {
|
||||
let r = pyitem.gen_module_item(ModuleItemArgs {
|
||||
item,
|
||||
attrs,
|
||||
context: &mut context,
|
||||
cfgs: cfgs.as_slice(),
|
||||
});
|
||||
context.errors.ok_or_push(r);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
context.errors.ok_or_push(r);
|
||||
}
|
||||
|
||||
// append additional items
|
||||
let module_name = context.name.as_str();
|
||||
let module_extend_items = context.module_extend_items.validate()?;
|
||||
let doc = doc.or_else(|| {
|
||||
crate::doc::Database::shared()
|
||||
.try_path(module_name)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(str::to_owned)
|
||||
});
|
||||
let doc = if let Some(doc) = doc {
|
||||
quote!(Some(#doc))
|
||||
} else {
|
||||
quote!(None)
|
||||
};
|
||||
items.extend(iter_chain![
|
||||
parse_quote! {
|
||||
pub(crate) const MODULE_NAME: &'static str = #module_name;
|
||||
},
|
||||
parse_quote! {
|
||||
pub(crate) const DOC: Option<&'static str> = #doc;
|
||||
},
|
||||
parse_quote! {
|
||||
pub(crate) fn extend_module(
|
||||
vm: &::rustpython_vm::VirtualMachine,
|
||||
module: &::rustpython_vm::PyObject,
|
||||
) {
|
||||
#module_extend_items
|
||||
}
|
||||
},
|
||||
parse_quote! {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn make_module(
|
||||
vm: &::rustpython_vm::VirtualMachine
|
||||
) -> ::rustpython_vm::PyObjectRef {
|
||||
let module = vm.new_module(MODULE_NAME, vm.ctx.new_dict(), DOC);
|
||||
extend_module(vm, &module);
|
||||
module
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
Ok(if let Some(error) = context.errors.into_error() {
|
||||
let error = Diagnostic::from(error);
|
||||
quote! {
|
||||
#module_item
|
||||
#error
|
||||
}
|
||||
} else {
|
||||
module_item.into_token_stream()
|
||||
})
|
||||
}
|
||||
|
||||
fn module_item_new(
|
||||
index: usize,
|
||||
attr_name: AttrName,
|
||||
pyattrs: Vec<usize>,
|
||||
) -> Box<dyn ModuleItem<AttrName = AttrName>> {
|
||||
match attr_name {
|
||||
AttrName::Function => Box::new(FunctionItem {
|
||||
inner: ContentItemInner { index, attr_name },
|
||||
pyattrs,
|
||||
}),
|
||||
AttrName::Attr => Box::new(AttributeItem {
|
||||
inner: ContentItemInner { index, attr_name },
|
||||
pyattrs,
|
||||
}),
|
||||
AttrName::Class => Box::new(ClassItem {
|
||||
inner: ContentItemInner { index, attr_name },
|
||||
pyattrs,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn attrs_to_module_items<F, R>(attrs: &[Attribute], item_new: F) -> Result<(Vec<R>, Vec<Attribute>)>
|
||||
where
|
||||
F: Fn(usize, AttrName, Vec<usize>) -> R,
|
||||
{
|
||||
let mut cfgs: Vec<Attribute> = Vec::new();
|
||||
let mut result = Vec::new();
|
||||
|
||||
let mut iter = attrs.iter().enumerate().peekable();
|
||||
while let Some((_, attr)) = iter.peek() {
|
||||
// take all cfgs but no py items
|
||||
let attr = *attr;
|
||||
if let Some(ident) = attr.get_ident() {
|
||||
let attr_name = ident.to_string();
|
||||
if attr_name == "cfg" {
|
||||
cfgs.push(attr.clone());
|
||||
} else if ALL_ALLOWED_NAMES.contains(&attr_name.as_str()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
iter.next();
|
||||
}
|
||||
|
||||
let mut closed = false;
|
||||
let mut pyattrs = Vec::new();
|
||||
for (i, attr) in iter {
|
||||
// take py items but no cfgs
|
||||
let attr_name = if let Some(ident) = attr.get_ident() {
|
||||
ident.to_string()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
if attr_name == "cfg" {
|
||||
bail_span!(attr, "#[py*] items must be placed under `cfgs`")
|
||||
}
|
||||
|
||||
let attr_name = match AttrName::from_str(attr_name.as_str()) {
|
||||
Ok(name) => name,
|
||||
Err(wrong_name) => {
|
||||
if !ALL_ALLOWED_NAMES.contains(&wrong_name.as_str()) {
|
||||
continue;
|
||||
} else if closed {
|
||||
bail_span!(attr, "Only one #[pyattr] annotated #[py*] item can exist")
|
||||
} else {
|
||||
bail_span!(attr, "#[pymodule] doesn't accept #[{}]", wrong_name)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if attr_name == AttrName::Attr {
|
||||
if !result.is_empty() {
|
||||
bail_span!(
|
||||
attr,
|
||||
"#[pyattr] must be placed on top of other #[py*] items",
|
||||
)
|
||||
}
|
||||
pyattrs.push(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if pyattrs.is_empty() {
|
||||
result.push(item_new(i, attr_name, Vec::new()));
|
||||
} else {
|
||||
match attr_name {
|
||||
AttrName::Class | AttrName::Function => {
|
||||
result.push(item_new(i, attr_name, pyattrs.clone()));
|
||||
}
|
||||
_ => {
|
||||
bail_span!(
|
||||
attr,
|
||||
"#[pyclass] or #[pyfunction] only can follow #[pyattr]",
|
||||
)
|
||||
}
|
||||
}
|
||||
pyattrs.clear();
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(last) = pyattrs.pop() {
|
||||
assert!(!closed);
|
||||
result.push(item_new(last, AttrName::Attr, pyattrs));
|
||||
}
|
||||
Ok((result, cfgs))
|
||||
}
|
||||
|
||||
/// #[pyfunction]
|
||||
struct FunctionItem {
|
||||
inner: ContentItemInner<AttrName>,
|
||||
pyattrs: Vec<usize>,
|
||||
}
|
||||
|
||||
/// #[pyclass]
|
||||
struct ClassItem {
|
||||
inner: ContentItemInner<AttrName>,
|
||||
pyattrs: Vec<usize>,
|
||||
}
|
||||
|
||||
/// #[pyattr]
|
||||
struct AttributeItem {
|
||||
inner: ContentItemInner<AttrName>,
|
||||
pyattrs: Vec<usize>,
|
||||
}
|
||||
|
||||
impl ContentItem for FunctionItem {
|
||||
type AttrName = AttrName;
|
||||
fn inner(&self) -> &ContentItemInner<AttrName> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl ContentItem for ClassItem {
|
||||
type AttrName = AttrName;
|
||||
fn inner(&self) -> &ContentItemInner<AttrName> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl ContentItem for AttributeItem {
|
||||
type AttrName = AttrName;
|
||||
fn inner(&self) -> &ContentItemInner<AttrName> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
struct ModuleItemArgs<'a> {
|
||||
item: &'a mut Item,
|
||||
attrs: &'a mut Vec<Attribute>,
|
||||
context: &'a mut ModuleContext,
|
||||
cfgs: &'a [Attribute],
|
||||
}
|
||||
|
||||
impl<'a> ModuleItemArgs<'a> {
|
||||
fn module_name(&'a self) -> &'a str {
|
||||
self.context.name.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
trait ModuleItem: ContentItem {
|
||||
fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()>;
|
||||
}
|
||||
|
||||
impl ModuleItem for FunctionItem {
|
||||
fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> {
|
||||
let func = args
|
||||
.item
|
||||
.function_or_method()
|
||||
.map_err(|_| self.new_syn_error(args.item.span(), "can only be on a function"))?;
|
||||
let ident = &func.sig().ident;
|
||||
|
||||
let item_attr = args.attrs.remove(self.index());
|
||||
let item_meta = SimpleItemMeta::from_attr(ident.clone(), &item_attr)?;
|
||||
|
||||
let py_name = item_meta.simple_name()?;
|
||||
let sig_doc = text_signature(func.sig(), &py_name);
|
||||
|
||||
let (tokens, py_names) = {
|
||||
let module = args.module_name();
|
||||
let doc = args.attrs.doc().or_else(|| {
|
||||
crate::doc::Database::shared()
|
||||
.try_module_item(module, &py_name)
|
||||
.ok() // TODO: doc must exist at least one of code or CPython
|
||||
.flatten()
|
||||
.map(str::to_owned)
|
||||
});
|
||||
let doc = if let Some(doc) = doc {
|
||||
format!("{}\n--\n\n{}", sig_doc, doc)
|
||||
} else {
|
||||
sig_doc
|
||||
};
|
||||
let doc = quote!(.with_doc(#doc.to_owned(), &vm.ctx));
|
||||
let new_func = quote_spanned!(ident.span()=>
|
||||
vm.ctx.make_funcdef(#py_name, #ident)
|
||||
#doc
|
||||
.into_function()
|
||||
.with_module(vm.new_pyobj(#module.to_owned()))
|
||||
.into_ref(&vm.ctx)
|
||||
);
|
||||
|
||||
if self.pyattrs.is_empty() {
|
||||
(
|
||||
quote_spanned! { ident.span() => {
|
||||
let func = #new_func;
|
||||
vm.__module_set_attr(module, #py_name, func).unwrap();
|
||||
}},
|
||||
vec![py_name],
|
||||
)
|
||||
} else {
|
||||
let mut py_names = HashSet::new();
|
||||
py_names.insert(py_name);
|
||||
for attr_index in self.pyattrs.iter().rev() {
|
||||
let mut loop_unit = || {
|
||||
let attr_attr = args.attrs.remove(*attr_index);
|
||||
let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr_attr)?;
|
||||
|
||||
let py_name = item_meta.simple_name()?;
|
||||
let inserted = py_names.insert(py_name.clone());
|
||||
if !inserted {
|
||||
return Err(self.new_syn_error(
|
||||
ident.span(),
|
||||
&format!(
|
||||
"`{py_name}` is duplicated name for multiple py* attribute"
|
||||
),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
let r = loop_unit();
|
||||
args.context.errors.ok_or_push(r);
|
||||
}
|
||||
let py_names: Vec<_> = py_names.into_iter().collect();
|
||||
(
|
||||
quote_spanned! { ident.span().resolved_at(Span::call_site()) => {
|
||||
let func = #new_func;
|
||||
for name in [#(#py_names,)*] {
|
||||
vm.__module_set_attr(module, name, func.clone()).unwrap();
|
||||
}
|
||||
}},
|
||||
py_names,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
args.context.module_extend_items.add_item(
|
||||
ident.clone(),
|
||||
py_names,
|
||||
args.cfgs.to_vec(),
|
||||
tokens,
|
||||
10,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleItem for ClassItem {
|
||||
fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> {
|
||||
let (ident, _) = pyclass_ident_and_attrs(args.item)?;
|
||||
let (class_name, class_new) = {
|
||||
let class_attr = &mut args.attrs[self.inner.index];
|
||||
let noattr = class_attr.try_remove_name("noattr")?;
|
||||
if self.pyattrs.is_empty() {
|
||||
// check noattr before ClassItemMeta::from_attr
|
||||
if noattr.is_none() {
|
||||
bail_span!(
|
||||
ident,
|
||||
"#[{name}] requires #[pyattr] to be a module attribute. \
|
||||
To keep it free type, try #[{name}(noattr)]",
|
||||
name = self.attr_name()
|
||||
)
|
||||
}
|
||||
}
|
||||
let noattr = noattr.is_some();
|
||||
let is_use = matches!(&args.item, syn::Item::Use(_));
|
||||
|
||||
let class_meta = ClassItemMeta::from_attr(ident.clone(), class_attr)?;
|
||||
let module_name = args.context.name.clone();
|
||||
let module_name = if let Some(class_module_name) = class_meta.module().ok().flatten() {
|
||||
class_module_name
|
||||
} else {
|
||||
class_attr.fill_nested_meta("module", || {
|
||||
parse_quote! {module = #module_name}
|
||||
})?;
|
||||
module_name
|
||||
};
|
||||
let class_name = if noattr && is_use {
|
||||
"<NO ATTR>".to_owned()
|
||||
} else {
|
||||
class_meta.class_name()?
|
||||
};
|
||||
let class_new = quote_spanned!(ident.span() =>
|
||||
let new_class = <#ident as ::rustpython_vm::class::PyClassImpl>::make_class(&vm.ctx);
|
||||
new_class.set_attr(rustpython_vm::identifier!(vm, __module__), vm.new_pyobj(#module_name));
|
||||
);
|
||||
(class_name, class_new)
|
||||
};
|
||||
|
||||
let mut py_names = Vec::new();
|
||||
for attr_index in self.pyattrs.iter().rev() {
|
||||
let mut loop_unit = || {
|
||||
let attr_attr = args.attrs.remove(*attr_index);
|
||||
let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr_attr)?;
|
||||
|
||||
let py_name = item_meta
|
||||
.optional_name()
|
||||
.unwrap_or_else(|| class_name.clone());
|
||||
py_names.push(py_name);
|
||||
|
||||
Ok(())
|
||||
};
|
||||
let r = loop_unit();
|
||||
args.context.errors.ok_or_push(r);
|
||||
}
|
||||
|
||||
let set_attr = match py_names.len() {
|
||||
0 => quote! {
|
||||
let _ = new_class; // suppress warning
|
||||
},
|
||||
1 => {
|
||||
let py_name = &py_names[0];
|
||||
quote! {
|
||||
vm.__module_set_attr(&module, #py_name, new_class).unwrap();
|
||||
}
|
||||
}
|
||||
_ => quote! {
|
||||
for name in [#(#py_names,)*] {
|
||||
vm.__module_set_attr(&module, name, new_class.clone()).unwrap();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
args.context.module_extend_items.add_item(
|
||||
ident.clone(),
|
||||
py_names,
|
||||
args.cfgs.to_vec(),
|
||||
quote_spanned! { ident.span() =>
|
||||
#class_new
|
||||
#set_attr
|
||||
},
|
||||
0,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleItem for AttributeItem {
|
||||
fn gen_module_item(&self, args: ModuleItemArgs<'_>) -> Result<()> {
|
||||
let cfgs = args.cfgs.to_vec();
|
||||
let attr = args.attrs.remove(self.index());
|
||||
let (ident, py_name, let_obj) = match args.item {
|
||||
Item::Fn(syn::ItemFn { sig, block, .. }) => {
|
||||
let ident = &sig.ident;
|
||||
// If `once` keyword is in #[pyattr],
|
||||
// wrapping it with static_cell for preventing it from using it as function
|
||||
let attr_meta = AttrItemMeta::from_attr(ident.clone(), &attr)?;
|
||||
if attr_meta.inner()._bool("once")? {
|
||||
let stmts = &block.stmts;
|
||||
let return_type = match &sig.output {
|
||||
syn::ReturnType::Default => {
|
||||
unreachable!("#[pyattr] attached function must have return type.")
|
||||
}
|
||||
syn::ReturnType::Type(_, ty) => ty,
|
||||
};
|
||||
let stmt: syn::Stmt = parse_quote! {
|
||||
{
|
||||
rustpython_common::static_cell! {
|
||||
static ERROR: #return_type;
|
||||
}
|
||||
ERROR
|
||||
.get_or_init(|| {
|
||||
#(#stmts)*
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
};
|
||||
block.stmts = vec![stmt];
|
||||
}
|
||||
|
||||
let py_name = attr_meta.simple_name()?;
|
||||
(
|
||||
ident.clone(),
|
||||
py_name,
|
||||
quote_spanned! { ident.span() =>
|
||||
let obj = vm.new_pyobj(#ident(vm));
|
||||
},
|
||||
)
|
||||
}
|
||||
Item::Const(syn::ItemConst { ident, .. }) => {
|
||||
let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr)?;
|
||||
let py_name = item_meta.simple_name()?;
|
||||
(
|
||||
ident.clone(),
|
||||
py_name,
|
||||
quote_spanned! { ident.span() =>
|
||||
let obj = vm.new_pyobj(#ident);
|
||||
},
|
||||
)
|
||||
}
|
||||
Item::Use(item) => {
|
||||
if !self.pyattrs.is_empty() {
|
||||
return Err(self
|
||||
.new_syn_error(item.span(), "Only single #[pyattr] is allowed for `use`"));
|
||||
}
|
||||
let _ = iter_use_idents(item, |ident, is_unique| {
|
||||
let item_meta = SimpleItemMeta::from_attr(ident.clone(), &attr)?;
|
||||
let py_name = if is_unique {
|
||||
item_meta.simple_name()?
|
||||
} else if item_meta.optional_name().is_some() {
|
||||
// this check actually doesn't need to be placed in loop
|
||||
return Err(self.new_syn_error(
|
||||
ident.span(),
|
||||
"`name` attribute is not allowed for multiple use items",
|
||||
));
|
||||
} else {
|
||||
ident.to_string()
|
||||
};
|
||||
let tokens = quote_spanned! { ident.span() =>
|
||||
vm.__module_set_attr(module, #py_name, vm.new_pyobj(#ident)).unwrap();
|
||||
};
|
||||
args.context.module_extend_items.add_item(
|
||||
ident.clone(),
|
||||
vec![py_name],
|
||||
cfgs.clone(),
|
||||
tokens,
|
||||
1,
|
||||
)?;
|
||||
Ok(())
|
||||
})?;
|
||||
return Ok(());
|
||||
}
|
||||
other => {
|
||||
return Err(
|
||||
self.new_syn_error(other.span(), "can only be on a function, const and use")
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let (tokens, py_names) = if self.pyattrs.is_empty() {
|
||||
(
|
||||
quote_spanned! { ident.span() => {
|
||||
#let_obj
|
||||
vm.__module_set_attr(module, #py_name, obj).unwrap();
|
||||
}},
|
||||
vec![py_name],
|
||||
)
|
||||
} else {
|
||||
let mut names = vec![py_name];
|
||||
for attr_index in self.pyattrs.iter().rev() {
|
||||
let mut loop_unit = || {
|
||||
let attr_attr = args.attrs.remove(*attr_index);
|
||||
let item_meta = AttrItemMeta::from_attr(ident.clone(), &attr_attr)?;
|
||||
if item_meta.inner()._bool("once")? {
|
||||
return Err(self.new_syn_error(
|
||||
ident.span(),
|
||||
"#[pyattr(once)] is only allowed for the bottom-most item",
|
||||
));
|
||||
}
|
||||
|
||||
let py_name = item_meta.optional_name().ok_or_else(|| {
|
||||
self.new_syn_error(
|
||||
ident.span(),
|
||||
"#[pyattr(name = ...)] is mandatory except for the bottom-most item",
|
||||
)
|
||||
})?;
|
||||
names.push(py_name);
|
||||
Ok(())
|
||||
};
|
||||
let r = loop_unit();
|
||||
args.context.errors.ok_or_push(r);
|
||||
}
|
||||
(
|
||||
quote_spanned! { ident.span() => {
|
||||
#let_obj
|
||||
for name in [(#(#names,)*)] {
|
||||
vm.__module_set_attr(module, name, obj.clone()).unwrap();
|
||||
}
|
||||
}},
|
||||
names,
|
||||
)
|
||||
};
|
||||
|
||||
args.context
|
||||
.module_extend_items
|
||||
.add_item(ident, py_names, cfgs, tokens, 1)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{DeriveInput, Result};
|
||||
|
||||
pub(crate) fn impl_pypayload(input: DeriveInput) -> Result<TokenStream> {
|
||||
let ty = &input.ident;
|
||||
|
||||
let ret = quote! {
|
||||
impl ::rustpython_vm::PyPayload for #ty {
|
||||
fn class(_vm: &::rustpython_vm::VirtualMachine) -> &'static rustpython_vm::Py<::rustpython_vm::builtins::PyType> {
|
||||
<Self as ::rustpython_vm::class::StaticType>::static_type()
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{DeriveInput, Ident, Result};
|
||||
|
||||
fn field_names(input: &DeriveInput) -> Result<Vec<&Ident>> {
|
||||
let fields = if let syn::Data::Struct(ref struc) = input.data {
|
||||
&struc.fields
|
||||
} else {
|
||||
bail_span!(
|
||||
input,
|
||||
"#[pystruct_sequence] can only be on a struct declaration"
|
||||
)
|
||||
};
|
||||
|
||||
let field_names: Vec<_> = match fields {
|
||||
syn::Fields::Named(fields) => fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|field| field.ident.as_ref().unwrap())
|
||||
.collect(),
|
||||
_ => bail_span!(
|
||||
input,
|
||||
"#[pystruct_sequence] can only be on a struct with named fields"
|
||||
),
|
||||
};
|
||||
|
||||
Ok(field_names)
|
||||
}
|
||||
|
||||
pub(crate) fn impl_pystruct_sequence(input: DeriveInput) -> Result<TokenStream> {
|
||||
let field_names = field_names(&input)?;
|
||||
let ty = &input.ident;
|
||||
let ret = quote! {
|
||||
impl ::rustpython_vm::types::PyStructSequence for #ty {
|
||||
const FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#field_names)),*];
|
||||
fn into_tuple(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::builtins::PyTuple {
|
||||
let items = vec![#(::rustpython_vm::convert::ToPyObject::to_pyobject(
|
||||
self.#field_names,
|
||||
vm,
|
||||
)),*];
|
||||
::rustpython_vm::builtins::PyTuple::new_unchecked(items.into_boxed_slice())
|
||||
}
|
||||
}
|
||||
impl ::rustpython_vm::convert::ToPyObject for #ty {
|
||||
fn to_pyobject(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::PyObjectRef {
|
||||
::rustpython_vm::types::PyStructSequence::into_struct_sequence(self, vm).into()
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub(crate) fn impl_pystruct_sequence_try_from_object(input: DeriveInput) -> Result<TokenStream> {
|
||||
let field_names = field_names(&input)?;
|
||||
let ty = &input.ident;
|
||||
let ret = quote! {
|
||||
impl ::rustpython_vm::TryFromObject for #ty {
|
||||
fn try_from_object(vm: &::rustpython_vm::VirtualMachine, seq: ::rustpython_vm::PyObjectRef) -> ::rustpython_vm::PyResult<Self> {
|
||||
const LEN: usize = #ty::FIELD_NAMES.len();
|
||||
let seq = Self::try_elements_from::<LEN>(seq, vm)?;
|
||||
// TODO: this is possible to be written without iterator
|
||||
let mut iter = seq.into_iter();
|
||||
Ok(Self {#(
|
||||
#field_names: iter.next().unwrap().clone().try_into_value(vm)?
|
||||
),*})
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
@@ -0,0 +1,586 @@
|
||||
use itertools::Itertools;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use syn::{
|
||||
spanned::Spanned, Attribute, Ident, Meta, MetaList, NestedMeta, Result, Signature, UseTree,
|
||||
};
|
||||
use syn_ext::{
|
||||
ext::{AttributeExt as SynAttributeExt, *},
|
||||
types::PunctuatedNestedMeta,
|
||||
};
|
||||
|
||||
pub(crate) const ALL_ALLOWED_NAMES: &[&str] = &[
|
||||
"pymethod",
|
||||
"pyclassmethod",
|
||||
"pystaticmethod",
|
||||
"pygetset",
|
||||
"pyfunction",
|
||||
"pyclass",
|
||||
"pyexception",
|
||||
"pystruct_sequence",
|
||||
"pyattr",
|
||||
"pyslot",
|
||||
"extend_class",
|
||||
"pymember",
|
||||
];
|
||||
|
||||
#[derive(Clone)]
|
||||
struct NurseryItem {
|
||||
attr_name: Ident,
|
||||
py_names: Vec<String>,
|
||||
cfgs: Vec<Attribute>,
|
||||
tokens: TokenStream,
|
||||
sort_order: usize,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ItemNursery(Vec<NurseryItem>);
|
||||
|
||||
pub(crate) struct ValidatedItemNursery(ItemNursery);
|
||||
|
||||
impl ItemNursery {
|
||||
pub fn add_item(
|
||||
&mut self,
|
||||
attr_name: Ident,
|
||||
py_names: Vec<String>,
|
||||
cfgs: Vec<Attribute>,
|
||||
tokens: TokenStream,
|
||||
sort_order: usize,
|
||||
) -> Result<()> {
|
||||
self.0.push(NurseryItem {
|
||||
attr_name,
|
||||
py_names,
|
||||
cfgs,
|
||||
tokens,
|
||||
sort_order,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate(self) -> Result<ValidatedItemNursery> {
|
||||
let mut by_name: HashSet<(String, Vec<Attribute>)> = HashSet::new();
|
||||
for item in &self.0 {
|
||||
for py_name in &item.py_names {
|
||||
let inserted = by_name.insert((py_name.clone(), item.cfgs.clone()));
|
||||
if !inserted {
|
||||
return Err(syn::Error::new(
|
||||
item.attr_name.span(),
|
||||
format!("Duplicated #[py*] attribute found for {:?}", &item.py_names),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ValidatedItemNursery(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ValidatedItemNursery {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let mut sorted = self.0 .0.clone();
|
||||
sorted.sort_by(|a, b| a.sort_order.cmp(&b.sort_order));
|
||||
tokens.extend(sorted.iter().map(|item| {
|
||||
let cfgs = &item.cfgs;
|
||||
let tokens = &item.tokens;
|
||||
quote! {
|
||||
#( #cfgs )*
|
||||
{
|
||||
#tokens
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ContentItemInner<T> {
|
||||
pub index: usize,
|
||||
pub attr_name: T,
|
||||
}
|
||||
|
||||
pub(crate) trait ContentItem {
|
||||
type AttrName: std::str::FromStr + std::fmt::Display;
|
||||
|
||||
fn inner(&self) -> &ContentItemInner<Self::AttrName>;
|
||||
fn index(&self) -> usize {
|
||||
self.inner().index
|
||||
}
|
||||
fn attr_name(&self) -> &Self::AttrName {
|
||||
&self.inner().attr_name
|
||||
}
|
||||
fn new_syn_error(&self, span: Span, message: &str) -> syn::Error {
|
||||
syn::Error::new(span, format!("#[{}] {}", self.attr_name(), message))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ItemMetaInner {
|
||||
pub item_ident: Ident,
|
||||
pub meta_ident: Ident,
|
||||
pub meta_map: HashMap<String, (usize, Meta)>,
|
||||
}
|
||||
|
||||
impl ItemMetaInner {
|
||||
pub fn from_nested<I>(
|
||||
item_ident: Ident,
|
||||
meta_ident: Ident,
|
||||
nested: I,
|
||||
allowed_names: &[&'static str],
|
||||
) -> Result<Self>
|
||||
where
|
||||
I: std::iter::Iterator<Item = NestedMeta>,
|
||||
{
|
||||
let (meta_map, lits) = nested.into_unique_map_and_lits(|path| {
|
||||
if let Some(ident) = path.get_ident() {
|
||||
let name = ident.to_string();
|
||||
if allowed_names.contains(&name.as_str()) {
|
||||
Ok(Some(name))
|
||||
} else {
|
||||
Err(err_span!(
|
||||
ident,
|
||||
"#[{meta_ident}({name})] is not one of allowed attributes [{}]",
|
||||
allowed_names.iter().format(", ")
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})?;
|
||||
if !lits.is_empty() {
|
||||
bail_span!(meta_ident, "#[{meta_ident}(..)] cannot contain literal")
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
item_ident,
|
||||
meta_ident,
|
||||
meta_map,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn item_name(&self) -> String {
|
||||
self.item_ident.to_string()
|
||||
}
|
||||
|
||||
pub fn meta_name(&self) -> String {
|
||||
self.meta_ident.to_string()
|
||||
}
|
||||
|
||||
pub fn _optional_str(&self, key: &str) -> Result<Option<String>> {
|
||||
let value = if let Some((_, meta)) = self.meta_map.get(key) {
|
||||
let Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(lit), ..
|
||||
}) = meta else {
|
||||
bail_span!(meta, "#[{}({} = ...)] must exist as a string", self.meta_name(), key)
|
||||
};
|
||||
Some(lit.value())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn _bool(&self, key: &str) -> Result<bool> {
|
||||
let value = if let Some((_, meta)) = self.meta_map.get(key) {
|
||||
match meta {
|
||||
Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Bool(lit),
|
||||
..
|
||||
}) => lit.value,
|
||||
Meta::Path(_) => true,
|
||||
_ => bail_span!(meta, "#[{}({})] is expected", self.meta_name(), key),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait ItemMeta: Sized {
|
||||
const ALLOWED_NAMES: &'static [&'static str];
|
||||
|
||||
fn from_attr(item_ident: Ident, attr: &Attribute) -> Result<Self> {
|
||||
let (meta_ident, nested) = attr.ident_and_promoted_nested()?;
|
||||
Self::from_nested(item_ident, meta_ident.clone(), nested.into_iter())
|
||||
}
|
||||
|
||||
fn from_nested<I>(item_ident: Ident, meta_ident: Ident, nested: I) -> Result<Self>
|
||||
where
|
||||
I: std::iter::Iterator<Item = NestedMeta>,
|
||||
{
|
||||
Ok(Self::from_inner(ItemMetaInner::from_nested(
|
||||
item_ident,
|
||||
meta_ident,
|
||||
nested,
|
||||
Self::ALLOWED_NAMES,
|
||||
)?))
|
||||
}
|
||||
|
||||
fn from_inner(inner: ItemMetaInner) -> Self;
|
||||
fn inner(&self) -> &ItemMetaInner;
|
||||
|
||||
fn simple_name(&self) -> Result<String> {
|
||||
let inner = self.inner();
|
||||
Ok(inner
|
||||
._optional_str("name")?
|
||||
.unwrap_or_else(|| inner.item_name()))
|
||||
}
|
||||
|
||||
fn optional_name(&self) -> Option<String> {
|
||||
self.inner()._optional_str("name").ok().flatten()
|
||||
}
|
||||
|
||||
fn new_meta_error(&self, msg: &str) -> syn::Error {
|
||||
let inner = self.inner();
|
||||
err_span!(inner.meta_ident, "#[{}] {}", inner.meta_name(), msg)
|
||||
}
|
||||
}
|
||||
pub(crate) struct SimpleItemMeta(pub ItemMetaInner);
|
||||
|
||||
impl ItemMeta for SimpleItemMeta {
|
||||
const ALLOWED_NAMES: &'static [&'static str] = &["name"];
|
||||
|
||||
fn from_inner(inner: ItemMetaInner) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
fn inner(&self) -> &ItemMetaInner {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AttrItemMeta(pub ItemMetaInner);
|
||||
|
||||
impl ItemMeta for AttrItemMeta {
|
||||
const ALLOWED_NAMES: &'static [&'static str] = &["name", "once"];
|
||||
|
||||
fn from_inner(inner: ItemMetaInner) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
fn inner(&self) -> &ItemMetaInner {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ClassItemMeta(ItemMetaInner);
|
||||
|
||||
impl ItemMeta for ClassItemMeta {
|
||||
const ALLOWED_NAMES: &'static [&'static str] = &["module", "name", "base", "metaclass"];
|
||||
|
||||
fn from_inner(inner: ItemMetaInner) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
fn inner(&self) -> &ItemMetaInner {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ClassItemMeta {
|
||||
pub fn class_name(&self) -> Result<String> {
|
||||
const KEY: &str = "name";
|
||||
let inner = self.inner();
|
||||
if let Some((_, meta)) = inner.meta_map.get(KEY) {
|
||||
match meta {
|
||||
Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
}) => return Ok(lit.value()),
|
||||
Meta::Path(_) => return Ok(inner.item_name()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
bail_span!(
|
||||
inner.meta_ident,
|
||||
"#[{attr_name}(name = ...)] must exist as a string. Try \
|
||||
#[{attr_name}(name)] to use rust type name.",
|
||||
attr_name = inner.meta_name()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn base(&self) -> Result<Option<String>> {
|
||||
self.inner()._optional_str("base")
|
||||
}
|
||||
|
||||
pub fn metaclass(&self) -> Result<Option<String>> {
|
||||
self.inner()._optional_str("metaclass")
|
||||
}
|
||||
|
||||
pub fn module(&self) -> Result<Option<String>> {
|
||||
const KEY: &str = "module";
|
||||
let inner = self.inner();
|
||||
let value = if let Some((_, meta)) = inner.meta_map.get(KEY) {
|
||||
match meta {
|
||||
Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
}) => Ok(Some(lit.value())),
|
||||
Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Bool(lit),
|
||||
..
|
||||
}) => if lit.value {
|
||||
Err(lit.span())
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
other => Err(other.span()),
|
||||
}
|
||||
} else {
|
||||
Err(inner.item_ident.span())
|
||||
}.map_err(|span| syn::Error::new(
|
||||
span,
|
||||
format!(
|
||||
"#[{attr_name}(module = ...)] must exist as a string or false. Try #[{attr_name}(module = false)] for built-in types.",
|
||||
attr_name=inner.meta_name()
|
||||
),
|
||||
))?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
// pub fn mandatory_module(&self) -> Result<String> {
|
||||
// let inner = self.inner();
|
||||
// let value = self.module().ok().flatten().
|
||||
// ok_or_else(|| err_span!(
|
||||
// inner.meta_ident,
|
||||
// "#[{attr_name}(module = ...)] must exist as a string. Built-in module is not allowed here.",
|
||||
// attr_name = inner.meta_name()
|
||||
// ))?;
|
||||
// Ok(value)
|
||||
// }
|
||||
}
|
||||
|
||||
pub(crate) trait AttributeExt: SynAttributeExt {
|
||||
fn promoted_nested(&self) -> Result<PunctuatedNestedMeta>;
|
||||
fn ident_and_promoted_nested(&self) -> Result<(&Ident, PunctuatedNestedMeta)>;
|
||||
fn try_remove_name(&mut self, name: &str) -> Result<Option<syn::NestedMeta>>;
|
||||
fn fill_nested_meta<F>(&mut self, name: &str, new_item: F) -> Result<()>
|
||||
where
|
||||
F: Fn() -> NestedMeta;
|
||||
}
|
||||
|
||||
impl AttributeExt for Attribute {
|
||||
fn promoted_nested(&self) -> Result<PunctuatedNestedMeta> {
|
||||
let list = self.promoted_list().map_err(|mut e| {
|
||||
let name = self.get_ident().unwrap().to_string();
|
||||
e.combine(err_span!(
|
||||
self,
|
||||
"#[{name} = \"...\"] cannot be a name/value, you probably meant \
|
||||
#[{name}(name = \"...\")]",
|
||||
));
|
||||
e
|
||||
})?;
|
||||
Ok(list.nested)
|
||||
}
|
||||
fn ident_and_promoted_nested(&self) -> Result<(&Ident, PunctuatedNestedMeta)> {
|
||||
Ok((self.get_ident().unwrap(), self.promoted_nested()?))
|
||||
}
|
||||
|
||||
fn try_remove_name(&mut self, item_name: &str) -> Result<Option<syn::NestedMeta>> {
|
||||
self.try_meta_mut(|meta| {
|
||||
let nested = match meta {
|
||||
Meta::List(MetaList { ref mut nested, .. }) => Ok(nested),
|
||||
other => Err(syn::Error::new(
|
||||
other.span(),
|
||||
format!(
|
||||
"#[{name}(...)] doesn't contain '{item}' to remove",
|
||||
name = other.get_ident().unwrap(),
|
||||
item = item_name
|
||||
),
|
||||
)),
|
||||
}?;
|
||||
|
||||
let mut found = None;
|
||||
for (i, item) in nested.iter().enumerate() {
|
||||
let ident = if let Some(ident) = item.get_ident() {
|
||||
ident
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
if *ident != item_name {
|
||||
continue;
|
||||
}
|
||||
if found.is_some() {
|
||||
return Err(syn::Error::new(
|
||||
item.span(),
|
||||
format!(
|
||||
"#[py..({}...)] must be unique but found multiple times",
|
||||
item_name,
|
||||
),
|
||||
));
|
||||
}
|
||||
found = Some(i);
|
||||
}
|
||||
|
||||
Ok(found.map(|idx| nested.remove(idx).into_value()))
|
||||
})
|
||||
}
|
||||
|
||||
fn fill_nested_meta<F>(&mut self, name: &str, new_item: F) -> Result<()>
|
||||
where
|
||||
F: Fn() -> NestedMeta,
|
||||
{
|
||||
self.try_meta_mut(|meta| {
|
||||
let list = meta.promote_to_list(Default::default())?;
|
||||
let has_name = list
|
||||
.nested
|
||||
.iter()
|
||||
.any(|nmeta| nmeta.get_path().map_or(false, |p| p.is_ident(name)));
|
||||
if !has_name {
|
||||
list.nested.push(new_item())
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pyclass_ident_and_attrs(item: &syn::Item) -> Result<(&Ident, &[Attribute])> {
|
||||
use syn::Item::*;
|
||||
Ok(match item {
|
||||
Struct(syn::ItemStruct { ident, attrs, .. }) => (ident, attrs),
|
||||
Enum(syn::ItemEnum { ident, attrs, .. }) => (ident, attrs),
|
||||
Use(item_use) => (
|
||||
iter_use_idents(item_use, |ident, _is_unique| Ok(ident))?
|
||||
.into_iter()
|
||||
.exactly_one()
|
||||
.map_err(|_| {
|
||||
err_span!(
|
||||
item_use,
|
||||
"#[pyclass] can only be on single name use statement",
|
||||
)
|
||||
})?,
|
||||
&item_use.attrs,
|
||||
),
|
||||
other => {
|
||||
bail_span!(
|
||||
other,
|
||||
"#[pyclass] can only be on a struct, enum or use declaration",
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) trait ErrorVec: Sized {
|
||||
fn into_error(self) -> Option<syn::Error>;
|
||||
fn into_result(self) -> Result<()> {
|
||||
if let Some(error) = self.into_error() {
|
||||
Err(error)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
fn ok_or_push<T>(&mut self, r: Result<T>) -> Option<T>;
|
||||
}
|
||||
|
||||
impl ErrorVec for Vec<syn::Error> {
|
||||
fn into_error(self) -> Option<syn::Error> {
|
||||
let mut iter = self.into_iter();
|
||||
if let Some(mut first) = iter.next() {
|
||||
for err in iter {
|
||||
first.combine(err);
|
||||
}
|
||||
Some(first)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn ok_or_push<T>(&mut self, r: Result<T>) -> Option<T> {
|
||||
match r {
|
||||
Ok(v) => Some(v),
|
||||
Err(e) => {
|
||||
self.push(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! iter_chain {
|
||||
($($it:expr),*$(,)?) => {
|
||||
::std::iter::empty()
|
||||
$(.chain(::std::iter::once($it)))*
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn iter_use_idents<'a, F, R: 'a>(item_use: &'a syn::ItemUse, mut f: F) -> Result<Vec<R>>
|
||||
where
|
||||
F: FnMut(&'a syn::Ident, bool) -> Result<R>,
|
||||
{
|
||||
let mut result = Vec::new();
|
||||
match &item_use.tree {
|
||||
UseTree::Name(name) => result.push(f(&name.ident, true)?),
|
||||
UseTree::Rename(rename) => result.push(f(&rename.rename, true)?),
|
||||
UseTree::Path(path) => match &*path.tree {
|
||||
UseTree::Name(name) => result.push(f(&name.ident, true)?),
|
||||
UseTree::Rename(rename) => result.push(f(&rename.rename, true)?),
|
||||
other => iter_use_tree_idents(other, &mut result, &mut f)?,
|
||||
},
|
||||
other => iter_use_tree_idents(other, &mut result, &mut f)?,
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn iter_use_tree_idents<'a, F, R: 'a>(
|
||||
tree: &'a syn::UseTree,
|
||||
result: &mut Vec<R>,
|
||||
f: &mut F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: FnMut(&'a syn::Ident, bool) -> Result<R>,
|
||||
{
|
||||
match tree {
|
||||
UseTree::Name(name) => result.push(f(&name.ident, false)?),
|
||||
UseTree::Rename(rename) => result.push(f(&rename.rename, false)?),
|
||||
UseTree::Path(path) => iter_use_tree_idents(&path.tree, result, f)?,
|
||||
UseTree::Group(syn::UseGroup { items, .. }) => {
|
||||
for subtree in items {
|
||||
iter_use_tree_idents(subtree, result, f)?;
|
||||
}
|
||||
}
|
||||
UseTree::Glob(glob) => {
|
||||
bail_span!(glob, "#[py*] doesn't allow '*'")
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Best effort attempt to generate a template from which a
|
||||
// __text_signature__ can be created.
|
||||
pub(crate) fn text_signature(sig: &Signature, name: &str) -> String {
|
||||
let signature = func_sig(sig);
|
||||
if signature.starts_with("$self") {
|
||||
format!("{}({})", name, signature)
|
||||
} else {
|
||||
format!("{}({}, {})", name, "$module", signature)
|
||||
}
|
||||
}
|
||||
|
||||
fn func_sig(sig: &Signature) -> String {
|
||||
sig.inputs
|
||||
.iter()
|
||||
.filter_map(|arg| {
|
||||
use syn::FnArg::*;
|
||||
let arg = match arg {
|
||||
Typed(typed) => typed,
|
||||
Receiver(_) => return Some("$self".to_owned()),
|
||||
};
|
||||
let ty = arg.ty.as_ref();
|
||||
let ty = quote!(#ty).to_string();
|
||||
if ty == "FuncArgs" {
|
||||
return Some("*args, **kwargs".to_owned());
|
||||
}
|
||||
if ty.starts_with('&') && ty.ends_with("VirtualMachine") {
|
||||
return None;
|
||||
}
|
||||
let ident = match arg.pat.as_ref() {
|
||||
syn::Pat::Ident(p) => p.ident.to_string(),
|
||||
// FIXME: other => unreachable!("function arg pattern must be ident but found `{}`", quote!(fn #ident(.. #other ..))),
|
||||
other => quote!(#other).to_string(),
|
||||
};
|
||||
if ident == "zelf" {
|
||||
return Some("$self".to_owned());
|
||||
}
|
||||
if ident == "vm" {
|
||||
unreachable!("type &VirtualMachine(`{}`) must be filtered already", ty);
|
||||
}
|
||||
Some(ident)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
Reference in new issue
Block a user