Skip to content

Commit 32ec98f

Browse files
committed
C3 algorithm
1 parent 6922f18 commit 32ec98f

File tree

2 files changed

+178
-3
lines changed

2 files changed

+178
-3
lines changed

src/singledispatch/core.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ impl SingleDispatchState {
7474
let cls_mro = get_obj_mro(&cls.clone())?;
7575
let mro = compose_mro(py, cls.clone(), self.registry.keys())?;
7676
let mut mro_match: Option<PyTypeReference> = None;
77+
eprintln!("Finding impl for {cls}");
7778
for typ in mro.iter() {
7879
if self.registry.contains_key(typ) {
7980
mro_match = Some(typ.clone_ref(py));
@@ -93,6 +94,7 @@ impl SingleDispatchState {
9394
)));
9495
}
9596
mro_match = Some(m.clone_ref(py));
97+
eprintln!("MRO match: {m}");
9698
break;
9799
}
98100
}
@@ -104,6 +106,7 @@ impl SingleDispatchState {
104106
Some(f) => Ok(f),
105107
None => {
106108
let obj_type = PyTypeReference::new(Builtins::cached(py).object_type.clone_ref(py));
109+
eprintln!("Found impl for {cls}: {obj_type}");
107110
match self.registry.get(&obj_type) {
108111
Some(it) => Ok(it.clone_ref(py)),
109112
None => Err(PyRuntimeError::new_err(format!(
@@ -117,6 +120,7 @@ impl SingleDispatchState {
117120
fn get_or_find_impl(&mut self, py: Python, cls: Bound<'_, PyAny>) -> PyResult<PyObject> {
118121
let free_cls = cls.unbind();
119122
let type_reference = PyTypeReference::new(free_cls.clone_ref(py));
123+
eprintln!("Finding impl {type_reference}");
120124

121125
match self.cache.get(&type_reference) {
122126
Some(handler) => Ok(handler.clone_ref(py)),
@@ -127,6 +131,7 @@ impl SingleDispatchState {
127131
};
128132
self.cache
129133
.insert(type_reference, handler_for_cls.clone_ref(py));
134+
eprintln!("Found new handler {handler_for_cls}");
130135
Ok(handler_for_cls)
131136
}
132137
}
@@ -224,6 +229,7 @@ impl SingleDispatch {
224229
args: &Bound<'_, PyTuple>,
225230
kwargs: Option<&Bound<'_, PyDict>>,
226231
) -> PyResult<Py<PyAny>> {
232+
eprintln!("Calling");
227233
match obj.getattr(intern!(py, "__class__")) {
228234
Ok(cls) => {
229235
let mut all_args = Vec::with_capacity(1 + args.len());
@@ -240,6 +246,7 @@ impl SingleDispatch {
240246
}
241247

242248
fn dispatch(&self, py: Python<'_>, cls: Bound<'_, PyAny>) -> PyResult<PyObject> {
249+
eprintln!("Dispatching");
243250
match self.lock.lock() {
244251
Ok(mut state) => {
245252
if let Some(cache_token) = &state.cache_token {

src/singledispatch/mro.rs

Lines changed: 171 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::singledispatch::builtins::Builtins;
22
use crate::singledispatch::typeref::PyTypeReference;
33
use crate::singledispatch::typing::TypingModule;
4+
use pyo3::exceptions::PyRuntimeError;
45
use pyo3::prelude::*;
56
use pyo3::types::PyTuple;
67
use pyo3::{intern, Bound, PyObject, PyResult, Python};
@@ -18,6 +19,20 @@ pub(crate) fn get_obj_mro(cls: &Bound<'_, PyAny>) -> PyResult<HashSet<PyTypeRefe
1819
Ok(mro)
1920
}
2021

22+
fn get_obj_bases(cls: &Bound<'_, PyAny>) -> PyResult<Vec<PyTypeReference>> {
23+
match cls.getattr_opt(intern!(cls.py(), "__bases__")) {
24+
Ok(opt) => match opt {
25+
Some(b) => Ok(b
26+
.downcast::<PyTuple>()?
27+
.iter()
28+
.map(|item| PyTypeReference::new(item.unbind()))
29+
.collect()),
30+
None => Ok(Vec::new()),
31+
},
32+
Err(e) => Err(e),
33+
}
34+
}
35+
2136
fn get_obj_subclasses(cls: &Bound<'_, PyAny>) -> PyResult<HashSet<PyTypeReference>> {
2237
let subclasses: HashSet<_> = cls
2338
.call_method0(intern!(cls.py(), "__subclasses__"))?
@@ -28,12 +43,165 @@ fn get_obj_subclasses(cls: &Bound<'_, PyAny>) -> PyResult<HashSet<PyTypeReferenc
2843
Ok(subclasses)
2944
}
3045

46+
fn find_merge_candidate(py: Python, seqs: &[&mut Vec<PyTypeReference>]) -> Option<PyTypeReference> {
47+
let mut candidate: Option<&PyTypeReference> = None;
48+
for i1 in 0..seqs.len() {
49+
let s1 = &seqs[i1];
50+
candidate = Some(&s1[0]);
51+
for i2 in 0..seqs.len() {
52+
let s2 = &seqs[i2];
53+
if s2[1..].contains(candidate.unwrap()) {
54+
candidate = None;
55+
break;
56+
}
57+
}
58+
if candidate.is_some() {
59+
break;
60+
}
61+
}
62+
candidate.map(|c| c.clone_ref(py))
63+
}
64+
65+
struct C3Mro<'a> {
66+
seqs: &'a mut Vec<&'a mut Vec<PyTypeReference>>,
67+
}
68+
69+
impl C3Mro<'_> {
70+
fn for_abcs<'a>(
71+
py: Python,
72+
abcs: &'a mut Vec<&'a mut Vec<PyTypeReference>>,
73+
) -> PyResult<Vec<PyTypeReference>> {
74+
C3Mro { seqs: abcs }.merge(py)
75+
}
76+
77+
fn merge(&mut self, py: Python) -> PyResult<Vec<PyTypeReference>> {
78+
let mut result: Vec<PyTypeReference> = Vec::new();
79+
loop {
80+
let seqs = &mut self.seqs;
81+
seqs.retain(|seq| !seq.is_empty());
82+
if seqs.is_empty() {
83+
return Ok(result);
84+
}
85+
match find_merge_candidate(py, seqs.as_slice()) {
86+
Some(c) => {
87+
for i in 0..seqs.len() {
88+
let seq = &mut self.seqs[i];
89+
if seq[0].eq(&c) {
90+
seq.remove(0);
91+
}
92+
}
93+
result.push(c);
94+
}
95+
None => return Err(PyRuntimeError::new_err("Inconsistent hierarchy")),
96+
}
97+
}
98+
}
99+
}
100+
101+
fn c3_boundary(py: Python, bases: &[PyTypeReference]) -> usize {
102+
let mut boundary = 0;
103+
104+
for (i, base) in bases.iter().rev().enumerate() {
105+
if base
106+
.wrapped()
107+
.bind(py)
108+
.hasattr(intern!(py, "__abstractmethods__"))
109+
.unwrap()
110+
{
111+
boundary = bases.len() - i;
112+
break;
113+
}
114+
}
115+
116+
boundary
117+
}
118+
31119
fn c3_mro(
32120
py: Python,
33-
cls: Bound<'_, PyAny>,
121+
cls: &Bound<'_, PyAny>,
34122
abcs: Vec<PyTypeReference>,
35123
) -> PyResult<Vec<PyTypeReference>> {
36-
Ok(abcs)
124+
let bases = match get_obj_bases(cls) {
125+
Ok(b) => {
126+
if !b.is_empty() {
127+
b
128+
} else {
129+
return Ok(Vec::new());
130+
}
131+
}
132+
Err(e) => return Err(e),
133+
};
134+
let boundary = c3_boundary(py, &bases);
135+
eprintln!("boundary = {boundary}");
136+
let base = &bases[boundary];
137+
138+
let (explicit_bases, other_bases) = bases.split_at(boundary);
139+
let abstract_bases: Vec<_> = abcs
140+
.iter()
141+
.flat_map(|abc| {
142+
if Builtins::cached(py)
143+
.issubclass(py, cls, base.wrapped().bind(py))
144+
.unwrap()
145+
&& !bases.iter().any(|b| {
146+
Builtins::cached(py)
147+
.issubclass(py, b.wrapped().bind(py), base.wrapped().bind(py))
148+
.unwrap()
149+
})
150+
{
151+
vec![abc]
152+
} else {
153+
vec![]
154+
}
155+
})
156+
.collect();
157+
158+
let new_abcs: Vec<_> = abcs.iter().filter(|c| abstract_bases.contains(c)).collect();
159+
160+
let mut mros: Vec<&mut Vec<PyTypeReference>> = Vec::new();
161+
162+
let mut cls_ref = vec![PyTypeReference::new(cls.clone().unbind())];
163+
mros.push(&mut cls_ref);
164+
165+
let mut explicit_bases_mro = Vec::from_iter(explicit_bases.iter().map(|b| {
166+
c3_mro(
167+
py,
168+
b.wrapped().bind(py),
169+
new_abcs.iter().map(|abc| abc.clone_ref(py)).collect(),
170+
)
171+
.unwrap()
172+
}));
173+
mros.extend(&mut explicit_bases_mro);
174+
175+
let mut abstract_bases_mro = Vec::from_iter(abstract_bases.iter().map(|b| {
176+
c3_mro(
177+
py,
178+
b.wrapped().bind(py),
179+
new_abcs.iter().map(|abc| abc.clone_ref(py)).collect(),
180+
)
181+
.unwrap()
182+
}));
183+
mros.extend(&mut abstract_bases_mro);
184+
185+
let mut other_bases_mro = Vec::from_iter(other_bases.iter().map(|b| {
186+
c3_mro(
187+
py,
188+
b.wrapped().bind(py),
189+
new_abcs.iter().map(|abc| abc.clone_ref(py)).collect(),
190+
)
191+
.unwrap()
192+
}));
193+
mros.extend(&mut other_bases_mro);
194+
195+
let mut explicit_bases_cloned = Vec::from_iter(explicit_bases.iter().map(|b| b.clone_ref(py)));
196+
mros.push(&mut explicit_bases_cloned);
197+
198+
let mut abstract_bases_cloned = Vec::from_iter(abstract_bases.iter().map(|b| b.clone_ref(py)));
199+
mros.push(&mut abstract_bases_cloned);
200+
201+
let mut other_bases_cloned = Vec::from_iter(other_bases.iter().map(|b| b.clone_ref(py)));
202+
mros.push(&mut other_bases_cloned);
203+
204+
C3Mro::for_abcs(py, &mut mros)
37205
}
38206

39207
pub(crate) fn compose_mro(
@@ -107,5 +275,5 @@ pub(crate) fn compose_mro(
107275
}
108276
});
109277

110-
c3_mro(py, cls, mro)
278+
c3_mro(py, &cls, mro)
111279
}

0 commit comments

Comments
 (0)