From 5133f81e6d38eab47b6dab89c0c6234fbd294c18 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Sat, 12 Dec 2020 09:50:25 +0000 Subject: [PATCH] deprecate pyclass name without quotes --- CHANGELOG.md | 2 +- pyo3-derive-backend/src/pyclass.rs | 32 +++++++++++++++++----------- pyo3-derive-backend/src/utils.rs | 4 ++-- tests/test_class_basics.rs | 7 +----- tests/ui/invalid_pyclass_args.rs | 6 ++++++ tests/ui/invalid_pyclass_args.stderr | 24 +++++++++++++++------ 6 files changed, 48 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec0fdcb6..0504e21c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add `PyAny::is_instance()` method. [#1276](https://github.com/PyO3/pyo3/pull/1276) - Add support for conversion between `char` and `PyString`. [#1282](https://github.com/PyO3/pyo3/pull/1282) - Add FFI definitions for `PyBuffer_SizeFromFormat`, `PyObject_LengthHint`, `PyObject_CallNoArgs`, `PyObject_CallOneArg`, `PyObject_CallMethodNoArgs`, `PyObject_CallMethodOneArg`, `PyObject_VectorcallDict`, and `PyObject_VectorcallMethod`. [#1287](https://github.com/PyO3/pyo3/pull/1287) -- Allow the use of a string literal in `#[pyclass(name = "string literal")]`. [#1295](https://github.com/PyO3/pyo3/pull/1295) - Add section about Python::check_signals to the FAQ page of the user guide. [#1301](https://github.com/PyO3/pyo3/pull/1301) ### Changed @@ -34,6 +33,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Rename `PyTypeInfo::is_instance` and `PyTypeInfo::is_exact_instance` to `PyTypeInfo::is_type_of` and `PyTypeInfo::is_exact_type_of`. [#1278](https://github.com/PyO3/pyo3/pull/1278) - Optimize `PyAny::call0`, `Py::call0` and `PyAny::call_method0` and `Py::call_method0` on Python 3.9 and up. [#1287](https://github.com/PyO3/pyo3/pull/1285) - Deprecate `Python::is_instance`, `Python::is_subclass`, `Python::release`, and `Python::xdecref`. [#1292](https://github.com/PyO3/pyo3/pull/1292) +- Require double-quotes for pyclass name argument e.g `#[pyclass(name = "MyClass")]`. [#1303](https://github.com/PyO3/pyo3/pull/1303) ### Removed - Remove deprecated ffi definitions `PyUnicode_AsUnicodeCopy`, `PyUnicode_GetMax`, `_Py_CheckRecursionLimit`, `PyObject_AsCharBuffer`, `PyObject_AsReadBuffer`, `PyObject_CheckReadBuffer` and `PyObject_AsWriteBuffer`, which will be removed in Python 3.10. [#1217](https://github.com/PyO3/pyo3/pull/1217) diff --git a/pyo3-derive-backend/src/pyclass.rs b/pyo3-derive-backend/src/pyclass.rs index c3ffb7be..57669ad5 100644 --- a/pyo3-derive-backend/src/pyclass.rs +++ b/pyo3-derive-backend/src/pyclass.rs @@ -15,7 +15,7 @@ use syn::{parse_quote, Expr, Token}; /// The parsed arguments of the pyclass macro pub struct PyClassArgs { pub freelist: Option, - pub name: Option, + pub name: Option, pub flags: Vec, pub base: syn::TypePath, pub has_extends: bool, @@ -96,15 +96,26 @@ impl PyClassArgs { lit: syn::Lit::Str(lit), .. }) => { - self.name = match lit.parse() { - Ok(name) => Some(name), - Err(..) => expected!("type name (e.g., Name or \"Name\")"), - } + self.name = Some(lit.parse().map_err(|_| { + syn::Error::new_spanned( + lit, + "expected a single identifier in double-quotes", + ) + })?); } syn::Expr::Path(exp) if exp.path.segments.len() == 1 => { - self.name = Some(exp.clone()); + return Err(syn::Error::new_spanned( + exp, + format!( + concat!( + "since PyO3 0.13 a pyclass name should be in double-quotes, ", + "e.g. \"{}\"" + ), + exp.path.get_ident().expect("path has 1 segment") + ), + )); } - _ => expected!("type name (e.g., Name or \"Name\")"), + _ => expected!("type name (e.g. \"Name\")"), }, "extends" => match &**right { syn::Expr::Path(exp) => { @@ -271,11 +282,8 @@ fn impl_proto_inventory(cls: &syn::Ident) -> TokenStream { } } -fn get_class_python_name(cls: &syn::Ident, attr: &PyClassArgs) -> TokenStream { - match &attr.name { - Some(name) => quote! { #name }, - None => quote! { #cls }, - } +fn get_class_python_name<'a>(cls: &'a syn::Ident, attr: &'a PyClassArgs) -> &'a syn::Ident { + attr.name.as_ref().unwrap_or(cls) } fn impl_class( diff --git a/pyo3-derive-backend/src/utils.rs b/pyo3-derive-backend/src/utils.rs index 8178a629..0ce2a5c7 100644 --- a/pyo3-derive-backend/src/utils.rs +++ b/pyo3-derive-backend/src/utils.rs @@ -76,9 +76,9 @@ fn parse_text_signature_attr( } } -pub fn parse_text_signature_attrs( +pub fn parse_text_signature_attrs( attrs: &mut Vec, - python_name: &T, + python_name: &syn::Ident, ) -> syn::Result> { let mut text_signature = None; let mut attrs_out = Vec::with_capacity(attrs.len()); diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index f1ab74ad..fcb49214 100644 --- a/tests/test_class_basics.rs +++ b/tests/test_class_basics.rs @@ -66,7 +66,7 @@ fn class_with_docstr() { } } -#[pyclass(name=CustomName)] +#[pyclass(name = "CustomName")] struct EmptyClass2 {} #[pymethods] @@ -79,17 +79,12 @@ impl EmptyClass2 { fn bar_static() {} } -#[pyclass(name = "CustomName")] -struct EmptyClass3 {} - #[test] fn custom_names() { let gil = Python::acquire_gil(); let py = gil.python(); let typeobj = py.get_type::(); - let typeobj2 = py.get_type::(); py_assert!(py, typeobj, "typeobj.__name__ == 'CustomName'"); - py_assert!(py, typeobj2, "typeobj2.__name__ == 'CustomName'"); py_assert!(py, typeobj, "typeobj.custom_fn.__name__ == 'custom_fn'"); py_assert!( py, diff --git a/tests/ui/invalid_pyclass_args.rs b/tests/ui/invalid_pyclass_args.rs index 57aa3a09..04560a04 100644 --- a/tests/ui/invalid_pyclass_args.rs +++ b/tests/ui/invalid_pyclass_args.rs @@ -9,6 +9,12 @@ struct InvalidExtends {} #[pyclass(name = m::MyClass)] struct InvalidName {} +#[pyclass(name = "Custom Name")] +struct InvalidName2 {} + +#[pyclass(name = CustomName)] +struct DeprecatedName {} + #[pyclass(module = my_module)] struct InvalidModule {} diff --git a/tests/ui/invalid_pyclass_args.stderr b/tests/ui/invalid_pyclass_args.stderr index 05de66a4..9953942e 100644 --- a/tests/ui/invalid_pyclass_args.stderr +++ b/tests/ui/invalid_pyclass_args.stderr @@ -10,20 +10,32 @@ error: Expected type path (e.g., my_mod::BaseClass) 6 | #[pyclass(extends = "PyDict")] | ^^^^^^^^ -error: Expected type name (e.g., Name or "Name") +error: Expected type name (e.g. "Name") --> $DIR/invalid_pyclass_args.rs:9:18 | 9 | #[pyclass(name = m::MyClass)] | ^^^^^^^^^^ -error: Expected string literal (e.g., "my_mod") - --> $DIR/invalid_pyclass_args.rs:12:20 +error: expected a single identifier in double-quotes + --> $DIR/invalid_pyclass_args.rs:12:18 | -12 | #[pyclass(module = my_module)] +12 | #[pyclass(name = "Custom Name")] + | ^^^^^^^^^^^^^ + +error: since PyO3 0.13 a pyclass name should be in double-quotes, e.g. "CustomName" + --> $DIR/invalid_pyclass_args.rs:15:18 + | +15 | #[pyclass(name = CustomName)] + | ^^^^^^^^^^ + +error: Expected string literal (e.g., "my_mod") + --> $DIR/invalid_pyclass_args.rs:18:20 + | +18 | #[pyclass(module = my_module)] | ^^^^^^^^^ error: Expected one of gc/weakref/subclass/dict/unsendable - --> $DIR/invalid_pyclass_args.rs:15:11 + --> $DIR/invalid_pyclass_args.rs:21:11 | -15 | #[pyclass(weakrev)] +21 | #[pyclass(weakrev)] | ^^^^^^^