Skip to content

Commit f64557c

Browse files
authored
Introspection: split module and name in decorators (#5618)
Introspection API: Introduces the PythonIdentifier struct to store an optional module name + a name inside the module or local
1 parent afefcce commit f64557c

File tree

8 files changed

+287
-183
lines changed

8 files changed

+287
-183
lines changed

newsfragments/5618.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Introspection: properly represent decorators as module + name (allows to get imports working)

pyo3-introspection/src/introspection.rs

Lines changed: 98 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::model::{
2-
Argument, Arguments, Attribute, Class, Function, Module, TypeHint, TypeHintExpr,
3-
VariableLengthArgument,
2+
Argument, Arguments, Attribute, Class, Function, Module, PythonIdentifier, TypeHint,
3+
TypeHintExpr, VariableLengthArgument,
44
};
55
use anyhow::{anyhow, bail, ensure, Context, Result};
66
use goblin::elf::section_header::SHN_XINDEX;
@@ -134,7 +134,7 @@ fn convert_members<'a>(
134134
parent: _,
135135
decorators,
136136
returns,
137-
} => functions.push(convert_function(name, arguments, decorators, returns)),
137+
} => functions.push(convert_function(name, arguments, decorators, returns)?),
138138
Chunk::Attribute {
139139
name,
140140
id: _,
@@ -149,14 +149,24 @@ fn convert_members<'a>(
149149
classes.sort_by(|l, r| l.name.cmp(&r.name));
150150
functions.sort_by(|l, r| match l.name.cmp(&r.name) {
151151
Ordering::Equal => {
152-
// We put the getter before the setter
153-
if l.decorators.iter().any(|d| d == "property") {
152+
// We put the getter before the setter. For that, we put @property before the other ones
153+
if l.decorators
154+
.iter()
155+
.any(|d| d.name == "property" && d.module.as_deref() == Some("builtins"))
156+
{
154157
Ordering::Less
155-
} else if r.decorators.iter().any(|d| d == "property") {
158+
} else if r
159+
.decorators
160+
.iter()
161+
.any(|d| d.name == "property" && d.module.as_deref() == Some("builtins"))
162+
{
156163
Ordering::Greater
157164
} else {
158165
// We pick an ordering based on decorators
159-
l.decorators.cmp(&r.decorators)
166+
l.decorators
167+
.iter()
168+
.map(|d| &d.name)
169+
.cmp(r.decorators.iter().map(|d| &d.name))
160170
}
161171
}
162172
o => o,
@@ -194,12 +204,27 @@ fn convert_class(
194204
fn convert_function(
195205
name: &str,
196206
arguments: &ChunkArguments,
197-
decorators: &[String],
207+
decorators: &[ChunkTypeHint],
198208
returns: &Option<ChunkTypeHint>,
199-
) -> Function {
200-
Function {
209+
) -> Result<Function> {
210+
Ok(Function {
201211
name: name.into(),
202-
decorators: decorators.to_vec(),
212+
decorators: decorators
213+
.iter()
214+
.map(|d| match convert_type_hint(d) {
215+
TypeHint::Plain(id) => Ok(PythonIdentifier {
216+
module: None,
217+
name: id.clone(),
218+
}),
219+
TypeHint::Ast(expr) => {
220+
if let TypeHintExpr::Identifier(i) = expr {
221+
Ok(i)
222+
} else {
223+
bail!("A decorator must be the identifier of a Python function")
224+
}
225+
}
226+
})
227+
.collect::<Result<_>>()?,
203228
arguments: Arguments {
204229
positional_only_arguments: arguments.posonlyargs.iter().map(convert_argument).collect(),
205230
arguments: arguments.args.iter().map(convert_argument).collect(),
@@ -214,7 +239,7 @@ fn convert_function(
214239
.map(convert_variable_length_argument),
215240
},
216241
returns: returns.as_ref().map(convert_type_hint),
217-
}
242+
})
218243
}
219244

220245
fn convert_argument(arg: &ChunkArgument) -> Argument {
@@ -253,15 +278,24 @@ fn convert_type_hint(arg: &ChunkTypeHint) -> TypeHint {
253278

254279
fn convert_type_hint_expr(expr: &ChunkTypeHintExpr) -> TypeHintExpr {
255280
match expr {
256-
ChunkTypeHintExpr::Local { id } => TypeHintExpr::Local { id: id.clone() },
257-
ChunkTypeHintExpr::Builtin { id } => TypeHintExpr::Builtin { id: id.clone() },
258-
ChunkTypeHintExpr::Attribute { module, attr } => TypeHintExpr::Attribute {
259-
module: module.clone(),
260-
attr: attr.clone(),
261-
},
262-
ChunkTypeHintExpr::Union { elts } => TypeHintExpr::Union {
263-
elts: elts.iter().map(convert_type_hint_expr).collect(),
264-
},
281+
ChunkTypeHintExpr::Local { id } => PythonIdentifier {
282+
module: None,
283+
name: id.clone(),
284+
}
285+
.into(),
286+
ChunkTypeHintExpr::Builtin { id } => PythonIdentifier {
287+
module: Some("builtins".into()),
288+
name: id.clone(),
289+
}
290+
.into(),
291+
ChunkTypeHintExpr::Attribute { module, attr } => PythonIdentifier {
292+
module: Some(module.clone()),
293+
name: attr.clone(),
294+
}
295+
.into(),
296+
ChunkTypeHintExpr::Union { elts } => {
297+
TypeHintExpr::Union(elts.iter().map(convert_type_hint_expr).collect())
298+
}
265299
ChunkTypeHintExpr::Subscript { value, slice } => TypeHintExpr::Subscript {
266300
value: Box::new(convert_type_hint_expr(value)),
267301
slice: slice.iter().map(convert_type_hint_expr).collect(),
@@ -419,8 +453,8 @@ enum Chunk {
419453
#[serde(default)]
420454
parent: Option<String>,
421455
#[serde(default)]
422-
decorators: Vec<String>,
423-
#[serde(default, deserialize_with = "deserialize_type_hint")]
456+
decorators: Vec<ChunkTypeHint>,
457+
#[serde(default)]
424458
returns: Option<ChunkTypeHint>,
425459
},
426460
Attribute {
@@ -431,7 +465,7 @@ enum Chunk {
431465
name: String,
432466
#[serde(default)]
433467
value: Option<String>,
434-
#[serde(default, deserialize_with = "deserialize_type_hint")]
468+
#[serde(default)]
435469
annotation: Option<ChunkTypeHint>,
436470
},
437471
}
@@ -455,7 +489,7 @@ struct ChunkArgument {
455489
name: String,
456490
#[serde(default)]
457491
default: Option<String>,
458-
#[serde(default, deserialize_with = "deserialize_type_hint")]
492+
#[serde(default)]
459493
annotation: Option<ChunkTypeHint>,
460494
}
461495

@@ -467,6 +501,45 @@ enum ChunkTypeHint {
467501
Plain(String),
468502
}
469503

504+
impl<'de> Deserialize<'de> for ChunkTypeHint {
505+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
506+
where
507+
D: Deserializer<'de>,
508+
{
509+
struct AnnotationVisitor;
510+
511+
impl<'de> Visitor<'de> for AnnotationVisitor {
512+
type Value = ChunkTypeHint;
513+
514+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
515+
formatter.write_str("annotation")
516+
}
517+
518+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
519+
where
520+
E: Error,
521+
{
522+
self.visit_string(v.into())
523+
}
524+
525+
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
526+
where
527+
E: Error,
528+
{
529+
Ok(ChunkTypeHint::Plain(v))
530+
}
531+
532+
fn visit_map<M: MapAccess<'de>>(self, map: M) -> Result<ChunkTypeHint, M::Error> {
533+
Ok(ChunkTypeHint::Ast(Deserialize::deserialize(
534+
MapAccessDeserializer::new(map),
535+
)?))
536+
}
537+
}
538+
539+
deserializer.deserialize_any(AnnotationVisitor)
540+
}
541+
}
542+
470543
#[derive(Deserialize)]
471544
#[serde(tag = "type", rename_all = "lowercase")]
472545
enum ChunkTypeHintExpr {
@@ -488,39 +561,3 @@ enum ChunkTypeHintExpr {
488561
slice: Vec<ChunkTypeHintExpr>,
489562
},
490563
}
491-
492-
fn deserialize_type_hint<'de, D: Deserializer<'de>>(
493-
deserializer: D,
494-
) -> Result<Option<ChunkTypeHint>, D::Error> {
495-
struct AnnotationVisitor;
496-
497-
impl<'de> Visitor<'de> for AnnotationVisitor {
498-
type Value = ChunkTypeHint;
499-
500-
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
501-
formatter.write_str("annotation")
502-
}
503-
504-
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
505-
where
506-
E: Error,
507-
{
508-
self.visit_string(v.into())
509-
}
510-
511-
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
512-
where
513-
E: Error,
514-
{
515-
Ok(ChunkTypeHint::Plain(v))
516-
}
517-
518-
fn visit_map<M: MapAccess<'de>>(self, map: M) -> Result<ChunkTypeHint, M::Error> {
519-
Ok(ChunkTypeHint::Ast(Deserialize::deserialize(
520-
MapAccessDeserializer::new(map),
521-
)?))
522-
}
523-
}
524-
525-
Ok(Some(deserializer.deserialize_any(AnnotationVisitor)?))
526-
}

pyo3-introspection/src/model.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub struct Class {
1919
pub struct Function {
2020
pub name: String,
2121
/// decorator like 'property' or 'staticmethod'
22-
pub decorators: Vec<String>,
22+
pub decorators: Vec<PythonIdentifier>,
2323
pub arguments: Arguments,
2424
/// return type
2525
pub returns: Option<TypeHint>,
@@ -77,17 +77,27 @@ pub enum TypeHint {
7777
/// A type hint annotation as an AST fragment
7878
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
7979
pub enum TypeHintExpr {
80-
/// A local identifier which module is unknown
81-
Local { id: String },
82-
/// A Python builtin like `int`
83-
Builtin { id: String },
84-
/// The attribute of a python object like `{value}.{attr}`
85-
Attribute { module: String, attr: String },
80+
/// An identifier
81+
Identifier(PythonIdentifier),
8682
/// A union `{left} | {right}`
87-
Union { elts: Vec<TypeHintExpr> },
83+
Union(Vec<TypeHintExpr>),
8884
/// A subscript `{value}[*slice]`
8985
Subscript {
9086
value: Box<TypeHintExpr>,
9187
slice: Vec<TypeHintExpr>,
9288
},
9389
}
90+
91+
impl From<PythonIdentifier> for TypeHintExpr {
92+
#[inline]
93+
fn from(value: PythonIdentifier) -> Self {
94+
Self::Identifier(value)
95+
}
96+
}
97+
98+
/// An Python identifier, either local (with `module = None`) or global (with `module = Some(_)`)
99+
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
100+
pub struct PythonIdentifier {
101+
pub module: Option<String>,
102+
pub name: String,
103+
}

0 commit comments

Comments
 (0)