Tidy up -derive, make use of let-else

This commit is contained in:
Noa
2022-12-19 18:47:40 -06:00
parent c01f014b12
commit 38a735af3b
9 changed files with 233 additions and 285 deletions

1
Cargo.lock generated
View File

@@ -2003,7 +2003,6 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"rustpython-codegen",
"rustpython-compiler",
"rustpython-compiler-core",
"rustpython-doc",

View File

@@ -11,7 +11,6 @@ edition = "2021"
proc-macro = true
[dependencies]
rustpython-codegen = { path = "../compiler/codegen", version = "0.1.1" }
rustpython-compiler = { path = "../compiler", version = "0.1.1" }
rustpython-compiler-core = { path = "../compiler/core", version = "0.1.1" }
rustpython-doc = { git = "https://github.com/RustPython/__doc__", branch = "main" }

View File

@@ -17,8 +17,6 @@ use crate::{extract_spans, Diagnostic};
use once_cell::sync::Lazy;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use rustpython_codegen as codegen;
use rustpython_compiler::compile;
use rustpython_compiler_core::{CodeObject, FrozenModule, Mode};
use std::{
collections::HashMap,
@@ -51,15 +49,25 @@ struct CompilationSource {
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> {
compile(source, mode, module_name, codegen::CompileOpts::default()).map_err(|err| {
compiler.compile(source, mode, module_name).map_err(|err| {
Diagnostic::spans_error(
self.span,
format!("Python compile error from {}: {}", origin(), err),
@@ -71,21 +79,30 @@ impl CompilationSource {
&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)
}
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)?,
code: self.compile_single(mode, module_name, compiler)?,
package: false,
},
}),
}
}
fn compile_single(&self, mode: Mode, module_name: String) -> Result<CodeObject, Diagnostic> {
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);
@@ -95,10 +112,10 @@ impl CompilationSource {
format!("Error reading file {:?}: {}", path, err),
)
})?;
self.compile_string(&source, mode, module_name, || rel_path.display())
self.compile_string(&source, mode, module_name, compiler, || rel_path.display())
}
CompilationSourceKind::SourceCode(code) => {
self.compile_string(&textwrap::dedent(code), mode, module_name, || {
self.compile_string(&textwrap::dedent(code), mode, module_name, compiler, || {
"string literal"
})
}
@@ -113,6 +130,7 @@ impl CompilationSource {
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)
@@ -144,6 +162,7 @@ impl CompilationSource {
format!("{}.{}", parent, file_name)
},
mode,
compiler,
)?);
} else if file_name.ends_with(".py") {
let stem = path.file_stem().unwrap().to_str().unwrap();
@@ -163,7 +182,7 @@ impl CompilationSource {
format!("Error reading file {:?}: {}", path, err),
)
})?;
self.compile_string(&source, mode, module_name.clone(), || {
self.compile_string(&source, mode, module_name.clone(), compiler, || {
path.strip_prefix(&*CARGO_MANIFEST_DIR)
.ok()
.unwrap_or(&path)
@@ -239,35 +258,28 @@ impl PyCompileInput {
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" {
match &name_value.lit {
Lit::Str(s) => match s.value().parse() {
Ok(mode_val) => mode = Some(mode_val),
Err(e) => bail_span!(s, "{}", e),
},
_ => bail_span!(name_value.lit, "mode must be a string"),
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(match &name_value.lit {
Lit::Str(s) => s.value(),
_ => bail_span!(name_value.lit, "module_name must be string"),
})
module_name = Some(check_str()?.value())
} else if ident == "source" {
assert_source_empty(&source)?;
let code = match &name_value.lit {
Lit::Str(s) => s.value(),
_ => bail_span!(name_value.lit, "source must be a string"),
};
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 = match &name_value.lit {
Lit::Str(s) => PathBuf::from(s.value()),
_ => bail_span!(name_value.lit, "source must be a string"),
};
let path = check_str()?.value().into();
source = Some(CompilationSource {
kind: CompilationSourceKind::File(path),
span: extract_spans(&name_value).unwrap(),
@@ -278,19 +290,13 @@ impl PyCompileInput {
}
assert_source_empty(&source)?;
let path = match &name_value.lit {
Lit::Str(s) => PathBuf::from(s.value()),
_ => bail_span!(name_value.lit, "source must be a string"),
};
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 = match &name_value.lit {
Lit::Str(s) => s.parse()?,
_ => bail_span!(name_value.lit, "source must be a string"),
};
let name = check_str()?.parse()?;
crate_name = Some(name);
}
}
@@ -351,12 +357,17 @@ struct PyCompileArgs {
crate_name: syn::Path,
}
pub fn impl_py_compile(input: TokenStream) -> Result<TokenStream, Diagnostic> {
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)?;
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());
@@ -369,12 +380,15 @@ pub fn impl_py_compile(input: TokenStream) -> Result<TokenStream, Diagnostic> {
Ok(output)
}
pub fn impl_py_freeze(input: TokenStream) -> Result<TokenStream, Diagnostic> {
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)?;
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)));

View File

@@ -33,7 +33,7 @@ use syn::parse::Error;
macro_rules! err_span {
($span:expr, $($msg:tt)*) => (
syn::Error::new_spanned(&$span, format!($($msg)*))
syn::Error::new_spanned(&$span, format_args!($($msg)*))
)
}

View File

@@ -1,4 +1,3 @@
use crate::util::path_eq;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
@@ -39,39 +38,39 @@ impl ArgAttribute {
if !attr.path.is_ident("pyarg") {
return None;
}
let inner = move || match attr.parse_meta()? {
Meta::List(list) => {
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)?;
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'."
)
})?;
Ok(attribute)
let mut attribute = ArgAttribute {
name: None,
kind,
default: None,
};
for arg in iter {
attribute.parse_argument(arg)?;
}
_ => bail_span!(attr, "pyarg must be a list, like #[pyarg(...)]"),
Ok(attribute)
};
Some(inner())
}
@@ -82,7 +81,7 @@ impl ArgAttribute {
}
match arg {
NestedMeta::Meta(Meta::Path(path)) => {
if path_eq(path, "default") || path_eq(path, "optional") {
if path.is_ident("default") || path.is_ident("optional") {
if self.default.is_none() {
self.default = Some(None);
}
@@ -91,7 +90,7 @@ impl ArgAttribute {
}
}
NestedMeta::Meta(Meta::NameValue(name_value)) => {
if path_eq(&name_value.path, "default") {
if name_value.path.is_ident("default") {
if matches!(self.default, Some(Some(_))) {
bail_span!(name_value, "Default already set");
}
@@ -100,7 +99,7 @@ impl ArgAttribute {
Lit::Str(ref val) => self.default = Some(Some(val.parse()?)),
_ => bail_span!(name_value, "Expected string value for default argument"),
}
} else if path_eq(&name_value.path, "name") {
} else if name_value.path.is_ident("name") {
if self.name.is_some() {
bail_span!(name_value, "already have a name")
}

View File

@@ -104,14 +104,30 @@ pub fn pystruct_sequence_try_from_object(
result_to_tokens(pystructseq::impl_pystruct_sequence_try_from_object(input))
}
// would be cool to move all the macro implementation to a separate rustpython-derive-shared
// that just depends on rustpython-compiler-core, and then rustpython-derive would hook -compiler
// up to it; so that (the bulk of) rustpython-derive and rustpython-codegen could build in parallel
struct Compiler;
impl compile_bytecode::Compiler for Compiler {
fn compile(
&self,
source: &str,
mode: rustpython_compiler_core::Mode,
module_name: String,
) -> Result<rustpython_compiler_core::CodeObject, Box<dyn std::error::Error>> {
use rustpython_compiler::{compile, CompileOpts};
Ok(compile(source, mode, module_name, CompileOpts::default())?)
}
}
#[proc_macro]
pub fn py_compile(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
result_to_tokens(compile_bytecode::impl_py_compile(input.into()))
result_to_tokens(compile_bytecode::impl_py_compile(input.into(), &Compiler))
}
#[proc_macro]
pub fn py_freeze(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
result_to_tokens(compile_bytecode::impl_py_freeze(input.into()))
result_to_tokens(compile_bytecode::impl_py_freeze(input.into(), &Compiler))
}
#[proc_macro_derive(PyPayload)]

View File

@@ -1,6 +1,6 @@
use super::Diagnostic;
use crate::util::{
path_eq, pyclass_ident_and_attrs, text_signature, ClassItemMeta, ContentItem, ContentItemInner,
pyclass_ident_and_attrs, text_signature, ClassItemMeta, ContentItem, ContentItemInner,
ErrorVec, ItemMeta, ItemMetaInner, ItemNursery, SimpleItemMeta, ALL_ALLOWED_NAMES,
};
use proc_macro2::{Span, TokenStream};
@@ -222,7 +222,7 @@ fn generate_class_def(
};
let basicsize = quote!(std::mem::size_of::<#ident>());
let is_pystruct = attrs.iter().any(|attr| {
path_eq(&attr.path, "derive")
attr.path.is_ident("derive")
&& if let Ok(Meta::List(l)) = attr.parse_meta() {
l.nested
.into_iter()
@@ -232,10 +232,7 @@ fn generate_class_def(
}
});
if base.is_some() && is_pystruct {
return Err(syn::Error::new_spanned(
ident,
"PyStructSequence cannot have `base` class attr",
));
bail_span!(ident, "PyStructSequence cannot have `base` class attr",);
}
let base_class = if is_pystruct {
Some(quote! { rustpython_vm::builtins::PyTuple })
@@ -331,9 +328,9 @@ pub(crate) fn impl_pyexception(attr: AttributeArgs, item: Item) -> Result<TokenS
// We also need to strip `Py` prefix from `class_name`,
// due to implementation and Python naming conventions mismatch:
// `PyKeyboardInterrupt` -> `KeyboardInterrupt`
let class_name = class_name.strip_prefix("Py").ok_or_else(|| {
syn::Error::new_spanned(&item, "We require 'class_name' to have 'Py' prefix")
})?;
let class_name = class_name
.strip_prefix("Py")
.ok_or_else(|| err_span!(item, "We require 'class_name' to have 'Py' prefix"))?;
// We just "proxy" it into `pyclass` macro, because, exception is a class.
let ret = quote! {
@@ -737,10 +734,7 @@ impl GetSetNursery {
) -> Result<()> {
assert!(!self.validated, "new item is not allowed after validation");
if !matches!(kind, GetSetItemKind::Get) && !cfgs.is_empty() {
return Err(syn::Error::new_spanned(
item_ident,
"Only the getter can have #[cfg]",
));
bail_span!(item_ident, "Only the getter can have #[cfg]",);
}
let entry = self.map.entry((name.clone(), cfgs)).or_default();
let func = match kind {
@@ -749,10 +743,11 @@ impl GetSetNursery {
GetSetItemKind::Delete => &mut entry.2,
};
if func.is_some() {
return Err(syn::Error::new_spanned(
bail_span!(
item_ident,
format!("Multiple property accessors with name '{}'", name),
));
"Multiple property accessors with name '{}'",
name
);
}
*func = Some(item_ident);
Ok(())
@@ -762,9 +757,10 @@ impl GetSetNursery {
let mut errors = Vec::new();
for ((name, _cfgs), (getter, setter, deleter)) in &self.map {
if getter.is_none() {
errors.push(syn::Error::new_spanned(
errors.push(err_span!(
setter.as_ref().or(deleter.as_ref()).unwrap(),
format!("GetSet '{}' is missing a getter", name),
"GetSet '{}' is missing a getter",
name
));
};
}
@@ -841,10 +837,7 @@ impl MemberNursery {
MemberItemKind::Set => &mut entry.1,
};
if func.is_some() {
return Err(syn::Error::new_spanned(
item_ident,
format!("Multiple member accessors with name '{}'", name),
));
bail_span!(item_ident, "Multiple member accessors with name '{}'", name);
}
*func = Some(item_ident);
Ok(())
@@ -854,9 +847,10 @@ impl MemberNursery {
let mut errors = Vec::new();
for ((name, _), (getter, setter)) in &self.map {
if getter.is_none() {
errors.push(syn::Error::new_spanned(
errors.push(err_span!(
setter.as_ref().unwrap(),
format!("Member '{}' is missing a getter", name),
"Member '{}' is missing a getter",
name
));
};
}
@@ -950,13 +944,11 @@ impl GetSetItemMeta {
(true, false) => GetSetItemKind::Set,
(false, true) => GetSetItemKind::Delete,
(true, true) => {
return Err(syn::Error::new_spanned(
bail_span!(
&inner.meta_ident,
format!(
"can't have both setter and deleter on a #[{}] fn",
inner.meta_name()
),
))
"can't have both setter and deleter on a #[{}] fn",
inner.meta_name()
)
}
};
let name = inner._optional_str("name")?;
@@ -967,27 +959,23 @@ impl GetSetItemMeta {
let extract_prefix_name = |prefix, item_typ| {
if let Some(name) = sig_name.strip_prefix(prefix) {
if name.is_empty() {
Err(syn::Error::new_spanned(
&inner.meta_ident,
format!(
"A #[{}({typ})] fn with a {prefix}* name must \
have something after \"{prefix}\"",
inner.meta_name(),
typ = item_typ,
prefix = prefix
),
Err(err_span!(
inner.meta_ident,
"A #[{}({typ})] fn with a {prefix}* name must \
have something after \"{prefix}\"",
inner.meta_name(),
typ = item_typ,
prefix = prefix
))
} else {
Ok(name.to_owned())
}
} else {
Err(syn::Error::new_spanned(
&inner.meta_ident,
format!(
"A #[{}(setter)] fn must either have a `name` \
parameter or a fn name along the lines of \"set_*\"",
inner.meta_name()
),
Err(err_span!(
inner.meta_ident,
"A #[{}(setter)] fn must either have a `name` \
parameter or a fn name along the lines of \"set_*\"",
inner.meta_name()
))
}
};
@@ -1025,17 +1013,14 @@ impl ItemMeta for SlotItemMeta {
} else {
Some(HashMap::default())
};
match (meta_map, nested.next()) {
(Some(meta_map), None) => Ok(Self::from_inner(ItemMetaInner {
item_ident,
meta_ident,
meta_map,
})),
_ => Err(syn::Error::new_spanned(
meta_ident,
"#[pyslot] must be of the form #[pyslot] or #[pyslot(slotname)]",
)),
}
let (Some(meta_map), None) = (meta_map, nested.next()) else {
bail_span!(meta_ident, "#[pyslot] must be of the form #[pyslot] or #[pyslot(slotname)]")
};
Ok(Self::from_inner(ItemMetaInner {
item_ident,
meta_ident,
meta_map,
}))
}
fn from_inner(inner: ItemMetaInner) -> Self {
@@ -1064,8 +1049,8 @@ impl SlotItemMeta {
Some(name)
};
slot_name.ok_or_else(|| {
syn::Error::new_spanned(
&inner.meta_ident,
err_span!(
inner.meta_ident,
"#[pyslot] must be of the form #[pyslot] or #[pyslot(slotname)]",
)
})
@@ -1092,27 +1077,23 @@ impl MemberItemMeta {
let extract_prefix_name = |prefix, item_typ| {
if let Some(name) = sig_name.strip_prefix(prefix) {
if name.is_empty() {
Err(syn::Error::new_spanned(
&inner.meta_ident,
format!(
"A #[{}({typ})] fn with a {prefix}* name must \
have something after \"{prefix}\"",
inner.meta_name(),
typ = item_typ,
prefix = prefix
),
Err(err_span!(
inner.meta_ident,
"A #[{}({typ})] fn with a {prefix}* name must \
have something after \"{prefix}\"",
inner.meta_name(),
typ = item_typ,
prefix = prefix
))
} else {
Ok(name.to_owned())
}
} else {
Err(syn::Error::new_spanned(
&inner.meta_ident,
format!(
"A #[{}(setter)] fn must either have a `name` \
parameter or a fn name along the lines of \"set_*\"",
inner.meta_name()
),
Err(err_span!(
inner.meta_ident,
"A #[{}(setter)] fn must either have a `name` \
parameter or a fn name along the lines of \"set_*\"",
inner.meta_name()
))
}
};
@@ -1159,15 +1140,12 @@ fn extract_impl_attrs(attr: AttributeArgs, item: &Ident) -> Result<ExtractedImpl
for attr in attr {
match attr {
NestedMeta::Meta(Meta::List(syn::MetaList { path, nested, .. })) => {
if path_eq(&path, "with") {
if path.is_ident("with") {
for meta in nested {
let path = match meta {
NestedMeta::Meta(Meta::Path(path)) => path,
meta => {
bail_span!(meta, "#[pyclass(with(...))] arguments should be paths")
}
let NestedMeta::Meta(Meta::Path(path)) = meta else {
bail_span!(meta, "#[pyclass(with(...))] arguments should be paths")
};
let (extend_class, extend_slots) = if path_eq(&path, "PyRef") {
let (extend_class, extend_slots) = if path.is_ident("PyRef") {
// special handling for PyRef
(
quote!(PyRef::<Self>::impl_extend_class),
@@ -1187,25 +1165,17 @@ fn extract_impl_attrs(attr: AttributeArgs, item: &Ident) -> Result<ExtractedImpl
#extend_slots(slots);
});
}
} else if path_eq(&path, "flags") {
} else if path.is_ident("flags") {
for meta in nested {
match meta {
NestedMeta::Meta(Meta::Path(path)) => {
if let Some(ident) = path.get_ident() {
flags.push(quote_spanned! { ident.span() =>
.union(::rustpython_vm::types::PyTypeFlags::#ident)
});
} else {
bail_span!(
path,
"#[pyclass(flags(...))] arguments should be ident"
)
}
}
meta => {
bail_span!(meta, "#[pyclass(flags(...))] arguments should be ident")
}
}
let NestedMeta::Meta(Meta::Path(path)) = meta else {
bail_span!(meta, "#[pyclass(flags(...))] arguments should be ident")
};
let ident = path.get_ident().ok_or_else(|| {
err_span!(path, "#[pyclass(flags(...))] arguments should be ident")
})?;
flags.push(quote_spanned! { ident.span() =>
.union(::rustpython_vm::types::PyTypeFlags::#ident)
});
}
} else {
bail_span!(path, "Unknown pyimpl attribute")
@@ -1295,19 +1265,13 @@ where
continue;
};
if attr_name == "cfg" {
return Err(syn::Error::new_spanned(
attr,
"#[py*] items must be placed under `cfgs`",
));
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(&attr_name.as_str()) {
return Err(syn::Error::new_spanned(
attr,
format!("#[pyclass] doesn't accept #[{}]", wrong_name),
));
bail_span!(attr, "#[pyclass] doesn't accept #[{}]", wrong_name)
} else {
continue;
}
@@ -1371,14 +1335,13 @@ fn parse_vec_ident(
) -> Result<String> {
Ok(attr
.get(index)
.ok_or_else(|| {
syn::Error::new_spanned(item, format!("We require {} argument to be set", &message))
})?
.ok_or_else(|| err_span!(item, "We require {} argument to be set", message))?
.get_ident()
.ok_or_else(|| {
syn::Error::new_spanned(
err_span!(
item,
format!("We require {} argument to be ident or string", &message),
"We require {} argument to be ident or string",
message
)
})?
.to_string())

View File

@@ -195,32 +195,28 @@ where
continue;
};
if attr_name == "cfg" {
return Err(syn::Error::new_spanned(
attr,
"#[py*] items must be placed under `cfgs`",
));
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) => {
let msg = if !ALL_ALLOWED_NAMES.contains(&wrong_name.as_str()) {
if !ALL_ALLOWED_NAMES.contains(&wrong_name.as_str()) {
continue;
} else if closed {
"Only one #[pyattr] annotated #[py*] item can exist".to_owned()
bail_span!(attr, "Only one #[pyattr] annotated #[py*] item can exist")
} else {
format!("#[pymodule] doesn't accept #[{}]", wrong_name)
};
return Err(syn::Error::new_spanned(attr, msg));
bail_span!(attr, "#[pymodule] doesn't accept #[{}]", wrong_name)
}
}
};
if attr_name == AttrName::Attr {
if !result.is_empty() {
return Err(syn::Error::new_spanned(
bail_span!(
attr,
"#[pyattr] must be placed on top of other #[py*] items",
));
)
}
pyattrs.push(i);
continue;
@@ -234,10 +230,10 @@ where
result.push(item_new(i, attr_name, pyattrs.clone()));
}
_ => {
return Err(syn::Error::new_spanned(
bail_span!(
attr,
"#[pyclass] or #[pyfunction] only can follow #[pyattr]",
));
)
}
}
pyattrs.clear();
@@ -409,14 +405,12 @@ impl ModuleItem for ClassItem {
if self.pyattrs.is_empty() {
// check noattr before ClassItemMeta::from_attr
if noattr.is_none() {
return Err(syn::Error::new_spanned(
bail_span!(
ident,
format!(
"#[{name}] requires #[pyattr] to be a module attribute. \
To keep it free type, try #[{name}(noattr)]",
name = self.attr_name()
),
));
"#[{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();

View File

@@ -3,8 +3,7 @@ use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use std::collections::{HashMap, HashSet};
use syn::{
spanned::Spanned, Attribute, Ident, Meta, MetaList, NestedMeta, Path, Result, Signature,
UseTree,
spanned::Spanned, Attribute, Ident, Meta, MetaList, NestedMeta, Result, Signature, UseTree,
};
use syn_ext::{
ext::{AttributeExt as SynAttributeExt, *},
@@ -136,12 +135,10 @@ impl ItemMetaInner {
if allowed_names.contains(&name.as_str()) {
Ok(Some(name))
} else {
Err(syn::Error::new_spanned(
Err(err_span!(
ident,
format!(
"#[{meta_ident}({name})] is not one of allowed attributes [{}]",
allowed_names.join(", ")
),
"#[{meta_ident}({name})] is not one of allowed attributes [{}]",
allowed_names.iter().format(", ")
))
}
} else {
@@ -149,10 +146,7 @@ impl ItemMetaInner {
}
})?;
if !lits.is_empty() {
return Err(syn::Error::new_spanned(
&meta_ident,
format!("#[{meta_ident}(..)] cannot contain literal"),
));
bail_span!(meta_ident, "#[{meta_ident}(..)] cannot contain literal")
}
Ok(Self {
@@ -172,22 +166,12 @@ impl ItemMetaInner {
pub fn _optional_str(&self, key: &str) -> Result<Option<String>> {
let value = if let Some((_, meta)) = self.meta_map.get(key) {
match meta {
Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(lit),
..
}) => Some(lit.value()),
other => {
return Err(syn::Error::new_spanned(
other,
format!(
"#[{}({} = ...)] must exist as a string",
self.meta_name(),
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
};
@@ -202,12 +186,7 @@ impl ItemMetaInner {
..
}) => lit.value,
Meta::Path(_) => true,
other => {
return Err(syn::Error::new_spanned(
other,
format!("#[{}({})] is expected", self.meta_name(), key),
))
}
_ => bail_span!(meta, "#[{}({})] is expected", self.meta_name(), key),
}
} else {
false
@@ -252,10 +231,7 @@ pub(crate) trait ItemMeta: Sized {
fn new_meta_error(&self, msg: &str) -> syn::Error {
let inner = self.inner();
syn::Error::new_spanned(
&inner.meta_ident,
format!("#[{}] {}", inner.meta_name(), msg),
)
err_span!(inner.meta_ident, "#[{}] {}", inner.meta_name(), msg)
}
}
pub(crate) struct SimpleItemMeta(pub ItemMetaInner);
@@ -301,25 +277,22 @@ impl ClassItemMeta {
pub fn class_name(&self) -> Result<String> {
const KEY: &str = "name";
let inner = self.inner();
let value = if let Some((_, meta)) = inner.meta_map.get(KEY) {
if let Some((_, meta)) = inner.meta_map.get(KEY) {
match meta {
Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(lit),
..
}) => Some(lit.value()),
Meta::Path(_) => Some(inner.item_name()),
_ => None,
}) => return Ok(lit.value()),
Meta::Path(_) => return Ok(inner.item_name()),
_ => {}
}
} else {
None
}.ok_or_else(|| syn::Error::new_spanned(
&inner.meta_ident,
format!(
"#[{attr_name}(name = ...)] must exist as a string. Try #[{attr_name}(name)] to use rust type name.",
attr_name=inner.meta_name()
),
))?;
Ok(value)
}
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>> {
@@ -364,21 +337,15 @@ impl ClassItemMeta {
// pub fn mandatory_module(&self) -> Result<String> {
// let inner = self.inner();
// let value = self.module().ok().flatten().
// ok_or_else(|| syn::Error::new_spanned(
// &inner.meta_ident,
// format!(
// "#[{attr_name}(module = ...)] must exist as a string. Built-in module is not allowed here.",
// attr_name=inner.meta_name()
// ),
// 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) fn path_eq(path: &Path, s: &str) -> bool {
path.get_ident().map_or(false, |id| id == s)
}
pub(crate) trait AttributeExt: SynAttributeExt {
fn promoted_nested(&self) -> Result<PunctuatedNestedMeta>;
fn ident_and_promoted_nested(&self) -> Result<(&Ident, PunctuatedNestedMeta)>;
@@ -392,13 +359,10 @@ 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(syn::Error::new_spanned(
e.combine(err_span!(
self,
format!(
"#[{name} = \"...\"] cannot be a name/value, you probably meant \
#[{name}(name = \"...\")]",
name = name,
),
"#[{name} = \"...\"] cannot be a name/value, you probably meant \
#[{name}(name = \"...\")]",
));
e
})?;
@@ -457,7 +421,7 @@ impl AttributeExt for Attribute {
let has_name = list
.nested
.iter()
.any(|nmeta| nmeta.get_path().map_or(false, |p| path_eq(p, name)));
.any(|nmeta| nmeta.get_path().map_or(false, |p| p.is_ident(name)));
if !has_name {
list.nested.push(new_item())
}
@@ -476,7 +440,7 @@ pub(crate) fn pyclass_ident_and_attrs(item: &syn::Item) -> Result<(&Ident, &[Att
.into_iter()
.exactly_one()
.map_err(|_| {
syn::Error::new_spanned(
err_span!(
item_use,
"#[pyclass] can only be on single name use statement",
)
@@ -484,10 +448,10 @@ pub(crate) fn pyclass_ident_and_attrs(item: &syn::Item) -> Result<(&Ident, &[Att
&item_use.attrs,
),
other => {
return Err(syn::Error::new_spanned(
bail_span!(
other,
"#[pyclass] can only be on a struct, enum or use declaration",
))
)
}
})
}
@@ -570,7 +534,7 @@ where
}
}
UseTree::Glob(glob) => {
return Err(syn::Error::new_spanned(glob, "#[py*] doesn't allow '*'"))
bail_span!(glob, "#[py*] doesn't allow '*'")
}
}
Ok(())