Skip to content

Commit 032d7fb

Browse files
authored
Merge branch 'PyO3:main' into disallow-races
2 parents 360a881 + 9aa53f4 commit 032d7fb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+602
-332
lines changed

.github/workflows/release.yaml

Lines changed: 0 additions & 26 deletions
This file was deleted.

.github/workflows/release.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Release Rust Crate
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
jobs:
9+
release:
10+
runs-on: ubuntu-latest
11+
environment: release
12+
steps:
13+
- uses: actions/checkout@v5
14+
15+
- uses: astral-sh/setup-uv@v6
16+
17+
- uses: rust-lang/crates-io-auth-action@v1
18+
id: auth
19+
20+
- name: Publish to crates.io
21+
run: nox -s publish
22+
env:
23+
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}

newsfragments/5338.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Introspection: support #[pyclass(eq, eq_int, ord, hash, str)]

newsfragments/5342.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `Python::try_attach`.

newsfragments/5351.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Introspection: copy annotations in some generic impls

newsfragments/5354.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rename `Python::assume_gil_acquired` to `Python::assume_attached`

pyo3-macros-backend/src/introspection.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ pub fn introspection_id_const() -> TokenStream {
547547
}
548548
}
549549

550-
fn unique_element_id() -> u64 {
550+
pub fn unique_element_id() -> u64 {
551551
let mut hasher = DefaultHasher::new();
552552
format!("{:?}", Span::call_site()).hash(&mut hasher); // Distinguishes between call sites
553553
GLOBAL_COUNTER_FOR_UNIQUE_NAMES

pyo3-macros-backend/src/pyclass.rs

Lines changed: 148 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,15 @@ use crate::attributes::{
1515
};
1616
use crate::combine_errors::CombineErrors;
1717
#[cfg(feature = "experimental-inspect")]
18-
use crate::introspection::{class_introspection_code, introspection_id_const};
18+
use crate::introspection::{
19+
class_introspection_code, function_introspection_code, introspection_id_const,
20+
unique_element_id,
21+
};
1922
use crate::konst::{ConstAttributes, ConstSpec};
2023
use crate::method::{FnArg, FnSpec, PyArg, RegularArg};
2124
use crate::pyfunction::ConstructorAttribute;
25+
#[cfg(feature = "experimental-inspect")]
26+
use crate::pyfunction::FunctionSignature;
2227
use crate::pyimpl::{gen_py_const, get_cfg_attributes, PyClassMethodsType};
2328
use crate::pymethod::{
2429
impl_py_class_attribute, impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef,
@@ -859,7 +864,20 @@ fn implement_py_formatting(
859864
fmt_impl
860865
}
861866
};
862-
let fmt_slot = generate_protocol_slot(ty, &mut fmt_impl, &__STR__, "__str__", ctx).unwrap();
867+
let fmt_slot = generate_protocol_slot(
868+
ty,
869+
&mut fmt_impl,
870+
&__STR__,
871+
"__str__",
872+
#[cfg(feature = "experimental-inspect")]
873+
FunctionIntrospectionData {
874+
names: &["__str__"],
875+
arguments: Vec::new(),
876+
returns: parse_quote! { ::std::string::String },
877+
},
878+
ctx,
879+
)
880+
.unwrap();
863881
(fmt_impl, fmt_slot)
864882
}
865883

@@ -935,8 +953,7 @@ fn impl_simple_enum(
935953
}
936954
}
937955
};
938-
let repr_slot =
939-
generate_default_protocol_slot(&ty, &mut repr_impl, &__REPR__, ctx).unwrap();
956+
let repr_slot = generate_default_protocol_slot(&ty, &mut repr_impl, &__REPR__, ctx)?;
940957
(repr_impl, repr_slot)
941958
};
942959

@@ -958,12 +975,18 @@ fn impl_simple_enum(
958975
}
959976
}
960977
};
961-
let int_slot = generate_default_protocol_slot(&ty, &mut int_impl, &__INT__, ctx).unwrap();
978+
let int_slot = generate_default_protocol_slot(&ty, &mut int_impl, &__INT__, ctx)?;
962979
(int_impl, int_slot)
963980
};
964981

965-
let (default_richcmp, default_richcmp_slot) =
966-
pyclass_richcmp_simple_enum(&args.options, &ty, repr_type, ctx)?;
982+
let (default_richcmp, default_richcmp_slot) = pyclass_richcmp_simple_enum(
983+
&args.options,
984+
&ty,
985+
repr_type,
986+
#[cfg(feature = "experimental-inspect")]
987+
&get_class_python_name(cls, args).to_string(),
988+
ctx,
989+
)?;
967990
let (default_hash, default_hash_slot) = pyclass_hash(&args.options, &ty, ctx)?;
968991

969992
let mut default_slots = vec![default_repr_slot, default_int_slot];
@@ -1070,12 +1093,18 @@ fn impl_complex_enum(
10701093
}
10711094
}
10721095
});
1073-
1096+
let output_type = if cfg!(feature = "experimental-inspect") {
1097+
let full_name = get_class_python_module_and_name(cls, &args);
1098+
quote! { const OUTPUT_TYPE: &'static str = #full_name; }
1099+
} else {
1100+
quote! {}
1101+
};
10741102
quote! {
10751103
impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls {
10761104
type Target = Self;
10771105
type Output = #pyo3_path::Bound<'py, <Self as #pyo3_path::conversion::IntoPyObject<'py>>::Target>;
10781106
type Error = #pyo3_path::PyErr;
1107+
#output_type
10791108

10801109
fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result<
10811110
<Self as #pyo3_path::conversion::IntoPyObject>::Output,
@@ -1462,20 +1491,59 @@ fn gen_complex_enum_variant_class_ident(enum_: &syn::Ident, variant: &syn::Ident
14621491
format_ident!("{}_{}", enum_, variant)
14631492
}
14641493

1494+
#[cfg(feature = "experimental-inspect")]
1495+
struct FunctionIntrospectionData<'a> {
1496+
names: &'a [&'a str],
1497+
arguments: Vec<FnArg<'a>>,
1498+
returns: syn::Type,
1499+
}
1500+
14651501
fn generate_protocol_slot(
14661502
cls: &syn::Type,
14671503
method: &mut syn::ImplItemFn,
14681504
slot: &SlotDef,
14691505
name: &str,
1506+
#[cfg(feature = "experimental-inspect")] introspection_data: FunctionIntrospectionData<'_>,
14701507
ctx: &Ctx,
14711508
) -> syn::Result<MethodAndSlotDef> {
14721509
let spec = FnSpec::parse(
14731510
&mut method.sig,
14741511
&mut Vec::new(),
14751512
PyFunctionOptions::default(),
1476-
)
1477-
.unwrap();
1478-
slot.generate_type_slot(&syn::parse_quote!(#cls), &spec, name, ctx)
1513+
)?;
1514+
#[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))]
1515+
let mut def = slot.generate_type_slot(&syn::parse_quote!(#cls), &spec, name, ctx)?;
1516+
#[cfg(feature = "experimental-inspect")]
1517+
{
1518+
// We generate introspection data
1519+
let associated_method = def.associated_method;
1520+
let signature = FunctionSignature::from_arguments(introspection_data.arguments);
1521+
let returns = introspection_data.returns;
1522+
let introspection = introspection_data
1523+
.names
1524+
.iter()
1525+
.map(|name| {
1526+
function_introspection_code(
1527+
&ctx.pyo3_path,
1528+
None,
1529+
name,
1530+
&signature,
1531+
Some("self"),
1532+
parse_quote!(-> #returns),
1533+
[],
1534+
Some(cls),
1535+
)
1536+
})
1537+
.collect::<Vec<_>>();
1538+
let const_name = format_ident!("_{}", unique_element_id()); // We need an explicit name here
1539+
def.associated_method = quote! {
1540+
#associated_method
1541+
const #const_name: () = {
1542+
#(#introspection)*
1543+
};
1544+
};
1545+
}
1546+
Ok(def)
14791547
}
14801548

14811549
fn generate_default_protocol_slot(
@@ -1488,8 +1556,7 @@ fn generate_default_protocol_slot(
14881556
&mut method.sig,
14891557
&mut Vec::new(),
14901558
PyFunctionOptions::default(),
1491-
)
1492-
.unwrap();
1559+
)?;
14931560
let name = spec.name.to_string();
14941561
slot.generate_type_slot(
14951562
&syn::parse_quote!(#cls),
@@ -1913,10 +1980,10 @@ fn pyclass_richcmp_simple_enum(
19131980
options: &PyClassPyO3Options,
19141981
cls: &syn::Type,
19151982
repr_type: &syn::Ident,
1983+
#[cfg(feature = "experimental-inspect")] class_name: &str,
19161984
ctx: &Ctx,
19171985
) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
19181986
let Ctx { pyo3_path, .. } = ctx;
1919-
19201987
if let Some(eq_int) = options.eq_int {
19211988
ensure_spanned!(options.eq.is_some(), eq_int.span() => "The `eq_int` option requires the `eq` option.");
19221989
}
@@ -1967,9 +2034,36 @@ fn pyclass_richcmp_simple_enum(
19672034
}
19682035
};
19692036
let richcmp_slot = if options.eq.is_some() {
1970-
generate_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, "__richcmp__", ctx).unwrap()
2037+
generate_protocol_slot(
2038+
cls,
2039+
&mut richcmp_impl,
2040+
&__RICHCMP__,
2041+
"__richcmp__",
2042+
#[cfg(feature = "experimental-inspect")]
2043+
FunctionIntrospectionData {
2044+
names: &["__eq__", "__ne__"],
2045+
arguments: vec![FnArg::Regular(RegularArg {
2046+
name: Cow::Owned(format_ident!("other")),
2047+
// we need to set a type, let's pick something small, it is overridden by annotation anyway
2048+
ty: &parse_quote!(!),
2049+
from_py_with: None,
2050+
default_value: None,
2051+
option_wrapped_type: None,
2052+
annotation: Some(match (options.eq.is_some(), options.eq_int.is_some()) {
2053+
(true, true) => {
2054+
format!("{class_name} | int")
2055+
}
2056+
(true, false) => class_name.into(),
2057+
(false, true) => "int".into(),
2058+
(false, false) => unreachable!(),
2059+
}),
2060+
})],
2061+
returns: parse_quote! { ::std::primitive::bool },
2062+
},
2063+
ctx,
2064+
)?
19712065
} else {
1972-
generate_default_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, ctx).unwrap()
2066+
generate_default_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, ctx)?
19732067
};
19742068
Ok((Some(richcmp_impl), Some(richcmp_slot)))
19752069
}
@@ -2004,9 +2098,30 @@ fn pyclass_richcmp(
20042098
}
20052099
}
20062100
};
2007-
let richcmp_slot =
2008-
generate_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, "__richcmp__", ctx)
2009-
.unwrap();
2101+
let richcmp_slot = generate_protocol_slot(
2102+
cls,
2103+
&mut richcmp_impl,
2104+
&__RICHCMP__,
2105+
"__richcmp__",
2106+
#[cfg(feature = "experimental-inspect")]
2107+
FunctionIntrospectionData {
2108+
names: if options.ord.is_some() {
2109+
&["__eq__", "__ne__", "__lt__", "__le__", "__gt__", "__ge__"]
2110+
} else {
2111+
&["__eq__", "__ne__"]
2112+
},
2113+
arguments: vec![FnArg::Regular(RegularArg {
2114+
name: Cow::Owned(format_ident!("other")),
2115+
ty: &parse_quote!(&#cls),
2116+
from_py_with: None,
2117+
default_value: None,
2118+
option_wrapped_type: None,
2119+
annotation: None,
2120+
})],
2121+
returns: parse_quote! { ::std::primitive::bool },
2122+
},
2123+
ctx,
2124+
)?;
20102125
Ok((Some(richcmp_impl), Some(richcmp_slot)))
20112126
} else {
20122127
Ok((None, None))
@@ -2033,8 +2148,19 @@ fn pyclass_hash(
20332148
::std::hash::Hasher::finish(&s)
20342149
}
20352150
};
2036-
let hash_slot =
2037-
generate_protocol_slot(cls, &mut hash_impl, &__HASH__, "__hash__", ctx).unwrap();
2151+
let hash_slot = generate_protocol_slot(
2152+
cls,
2153+
&mut hash_impl,
2154+
&__HASH__,
2155+
"__hash__",
2156+
#[cfg(feature = "experimental-inspect")]
2157+
FunctionIntrospectionData {
2158+
names: &["__hash__"],
2159+
arguments: Vec::new(),
2160+
returns: parse_quote! { ::std::primitive::u64 },
2161+
},
2162+
ctx,
2163+
)?;
20382164
Ok((Some(hash_impl), Some(hash_slot)))
20392165
}
20402166
None => Ok((None, None)),
@@ -2462,7 +2588,7 @@ impl<'a> PyClassImplsBuilder<'a> {
24622588
let cls = self.cls;
24632589
let Ctx { pyo3_path, .. } = ctx;
24642590

2465-
self.attr.options.freelist.as_ref().map_or(quote!{}, |freelist| {
2591+
self.attr.options.freelist.as_ref().map_or(quote! {}, |freelist| {
24662592
let freelist = &freelist.value;
24672593
quote! {
24682594
impl #pyo3_path::impl_::pyclass::PyClassWithFreeList for #cls {

0 commit comments

Comments
 (0)