Skip to content

Commit 4ece557

Browse files
committed
Introduce TypeHint struct
inspect::TypeHint is composed of an "annotation" string and a list of "imports" ("from X import Y" kind) The type is expected to be built using the macros `type_hint!(module, name)`, `type_hint_union!(*args)` and `type_hint_subscript(main, *args)` that take care of maintaining the import list Introspection data generation is done using the hidden type_hint_json macro to avoid that the proc macros generate too much code Sadly, outside `type_hint` these macros can't be converted into const functions because they need to do some concatenation. I introduced `type_hint!` for consistency, happy to convert it to a const function. Miscellaneous changes: - Rename PyType{Info,Check}::TYPE_INFO into TYPE_HINT - Drop redundant PyClassImpl::TYPE_NAME
1 parent 0e6e4f4 commit 4ece557

30 files changed

+759
-394
lines changed

newsfragments/5438.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Introspection: introduce `TypeHint` and make use of it to encode type hint annotations.

pyo3-introspection/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ anyhow = "1"
1313
goblin = ">=0.9, <0.11"
1414
serde = { version = "1", features = ["derive"] }
1515
serde_json = "1"
16-
unicode-ident = "1"
1716

1817
[dev-dependencies]
1918
tempfile = "3.12.0"

pyo3-introspection/src/introspection.rs

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::model::{
2-
Argument, Arguments, Attribute, Class, Function, Module, VariableLengthArgument,
2+
Argument, Arguments, Attribute, Class, Function, Module, TypeHint, TypeHintImport,
3+
VariableLengthArgument,
34
};
45
use anyhow::{bail, ensure, Context, Result};
56
use goblin::elf::Elf;
@@ -8,11 +9,12 @@ use goblin::mach::symbols::{NO_SECT, N_SECT};
89
use goblin::mach::{Mach, MachO, SingleArch};
910
use goblin::pe::PE;
1011
use goblin::Object;
11-
use serde::Deserialize;
12+
use serde::de::{Error, MapAccess, Visitor};
13+
use serde::{de, Deserialize, Deserializer};
1214
use std::cmp::Ordering;
1315
use std::collections::HashMap;
14-
use std::fs;
1516
use std::path::Path;
17+
use std::{fmt, fs};
1618

1719
/// Introspect a cdylib built with PyO3 and returns the definition of a Python module.
1820
///
@@ -191,7 +193,7 @@ fn convert_function(
191193
name: &str,
192194
arguments: &ChunkArguments,
193195
decorators: &[String],
194-
returns: &Option<String>,
196+
returns: &Option<ChunkTypeHint>,
195197
) -> Function {
196198
Function {
197199
name: name.into(),
@@ -209,30 +211,48 @@ fn convert_function(
209211
.as_ref()
210212
.map(convert_variable_length_argument),
211213
},
212-
returns: returns.clone(),
214+
returns: returns.as_ref().map(convert_type_hint),
213215
}
214216
}
215217

216218
fn convert_argument(arg: &ChunkArgument) -> Argument {
217219
Argument {
218220
name: arg.name.clone(),
219221
default_value: arg.default.clone(),
220-
annotation: arg.annotation.clone(),
222+
annotation: arg.annotation.as_ref().map(convert_type_hint),
221223
}
222224
}
223225

224226
fn convert_variable_length_argument(arg: &ChunkArgument) -> VariableLengthArgument {
225227
VariableLengthArgument {
226228
name: arg.name.clone(),
227-
annotation: arg.annotation.clone(),
229+
annotation: arg.annotation.as_ref().map(convert_type_hint),
228230
}
229231
}
230232

231-
fn convert_attribute(name: &str, value: &Option<String>, annotation: &Option<String>) -> Attribute {
233+
fn convert_attribute(
234+
name: &str,
235+
value: &Option<String>,
236+
annotation: &Option<ChunkTypeHint>,
237+
) -> Attribute {
232238
Attribute {
233239
name: name.into(),
234240
value: value.clone(),
235-
annotation: annotation.clone(),
241+
annotation: annotation.as_ref().map(convert_type_hint),
242+
}
243+
}
244+
245+
fn convert_type_hint(arg: &ChunkTypeHint) -> TypeHint {
246+
TypeHint {
247+
annotation: arg.annotation.clone(),
248+
imports: arg.imports.iter().map(convert_type_hint_import).collect(),
249+
}
250+
}
251+
252+
fn convert_type_hint_import(arg: &ChunkTypeHintImport) -> TypeHintImport {
253+
TypeHintImport {
254+
module: arg.module.clone(),
255+
name: arg.name.clone(),
236256
}
237257
}
238258

@@ -414,8 +434,8 @@ enum Chunk {
414434
parent: Option<String>,
415435
#[serde(default)]
416436
decorators: Vec<String>,
417-
#[serde(default)]
418-
returns: Option<String>,
437+
#[serde(default, deserialize_with = "deserialize_annotation")]
438+
returns: Option<ChunkTypeHint>,
419439
},
420440
Attribute {
421441
#[serde(default)]
@@ -425,8 +445,8 @@ enum Chunk {
425445
name: String,
426446
#[serde(default)]
427447
value: Option<String>,
428-
#[serde(default)]
429-
annotation: Option<String>,
448+
#[serde(default, deserialize_with = "deserialize_annotation")]
449+
annotation: Option<ChunkTypeHint>,
430450
},
431451
}
432452

@@ -449,6 +469,56 @@ struct ChunkArgument {
449469
name: String,
450470
#[serde(default)]
451471
default: Option<String>,
472+
#[serde(default, deserialize_with = "deserialize_annotation")]
473+
annotation: Option<ChunkTypeHint>,
474+
}
475+
476+
#[derive(Deserialize)]
477+
struct ChunkTypeHint {
478+
annotation: String,
452479
#[serde(default)]
453-
annotation: Option<String>,
480+
imports: Vec<ChunkTypeHintImport>,
481+
}
482+
483+
#[derive(Deserialize)]
484+
struct ChunkTypeHintImport {
485+
module: String,
486+
name: String,
487+
}
488+
489+
fn deserialize_annotation<'de, D: Deserializer<'de>>(
490+
deserializer: D,
491+
) -> Result<Option<ChunkTypeHint>, D::Error> {
492+
struct AnnotationVisitor;
493+
494+
impl<'de> Visitor<'de> for AnnotationVisitor {
495+
type Value = ChunkTypeHint;
496+
497+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
498+
formatter.write_str("annotation")
499+
}
500+
501+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
502+
where
503+
E: Error,
504+
{
505+
self.visit_string(v.into())
506+
}
507+
508+
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
509+
where
510+
E: Error,
511+
{
512+
Ok(ChunkTypeHint {
513+
annotation: v,
514+
imports: Vec::new(),
515+
})
516+
}
517+
518+
fn visit_map<M: MapAccess<'de>>(self, map: M) -> Result<ChunkTypeHint, M::Error> {
519+
Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
520+
}
521+
}
522+
523+
Ok(Some(deserializer.deserialize_any(AnnotationVisitor)?))
454524
}

pyo3-introspection/src/model.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub struct Function {
2222
pub decorators: Vec<String>,
2323
pub arguments: Arguments,
2424
/// return type
25-
pub returns: Option<String>,
25+
pub returns: Option<TypeHint>,
2626
}
2727

2828
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
@@ -31,7 +31,7 @@ pub struct Attribute {
3131
/// Value as a Python expression if easily expressible
3232
pub value: Option<String>,
3333
/// Type annotation as a Python expression
34-
pub annotation: Option<String>,
34+
pub annotation: Option<TypeHint>,
3535
}
3636

3737
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
@@ -54,13 +54,27 @@ pub struct Argument {
5454
/// Default value as a Python expression
5555
pub default_value: Option<String>,
5656
/// Type annotation as a Python expression
57-
pub annotation: Option<String>,
57+
pub annotation: Option<TypeHint>,
5858
}
5959

6060
/// A variable length argument ie. *vararg or **kwarg
6161
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
6262
pub struct VariableLengthArgument {
6363
pub name: String,
6464
/// Type annotation as a Python expression
65-
pub annotation: Option<String>,
65+
pub annotation: Option<TypeHint>,
66+
}
67+
68+
/// A type hint annotation with the required modules to import
69+
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
70+
pub struct TypeHint {
71+
pub annotation: String,
72+
pub imports: Vec<TypeHintImport>,
73+
}
74+
75+
/// An import required to make the type hint valid like `from {module} import {name}`
76+
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
77+
pub struct TypeHintImport {
78+
pub module: String,
79+
pub name: String,
6680
}

0 commit comments

Comments
 (0)