Skip to content

Commit 329b239

Browse files
committed
Fixes #2: auto generate python files and fix type ref
1 parent cdc574d commit 329b239

10 files changed

Lines changed: 144 additions & 25 deletions

File tree

pyo3-stub-gen-derive/src/gen_stub/stub_type.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl ToTokens for StubType {
2121
#[automatically_derived]
2222
impl ::pyo3_stub_gen::PyStubType for #ty {
2323
fn type_output() -> ::pyo3_stub_gen::TypeInfo {
24-
::pyo3_stub_gen::TypeInfo::with_module(#name, #module_tt)
24+
::pyo3_stub_gen::TypeInfo::with_type(#name, #module_tt)
2525
}
2626
}
2727
})

pyo3-stub-gen/src/generate.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ pub use module::*;
2323
pub use stub_info::*;
2424
pub use variable::*;
2525

26-
use crate::stub_type::ModuleRef;
26+
use crate::stub_type::CommonRef;
2727
use std::collections::HashSet;
2828

2929
fn indent() -> &'static str {
3030
" "
3131
}
3232

3333
pub trait Import {
34-
fn import(&self) -> HashSet<ModuleRef>;
34+
fn import(&self) -> HashSet<CommonRef>;
3535
}

pyo3-stub-gen/src/generate/arg.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{generate::Import, stub_type::ModuleRef, type_info::*, TypeInfo};
1+
use crate::{generate::Import, stub_type::CommonRef, type_info::*, TypeInfo};
22
use std::{collections::HashSet, fmt};
33

44
#[derive(Debug, Clone, PartialEq)]
@@ -9,7 +9,7 @@ pub struct Arg {
99
}
1010

1111
impl Import for Arg {
12-
fn import(&self) -> HashSet<ModuleRef> {
12+
fn import(&self) -> HashSet<CommonRef> {
1313
self.r#type.import.clone()
1414
}
1515
}

pyo3-stub-gen/src/generate/class.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub struct ClassDef {
1212
}
1313

1414
impl Import for ClassDef {
15-
fn import(&self) -> HashSet<ModuleRef> {
15+
fn import(&self) -> HashSet<CommonRef> {
1616
let mut import = HashSet::new();
1717
for base in &self.bases {
1818
import.extend(base.import.clone());

pyo3-stub-gen/src/generate/function.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub struct FunctionDef {
1111
}
1212

1313
impl Import for FunctionDef {
14-
fn import(&self) -> HashSet<ModuleRef> {
14+
fn import(&self) -> HashSet<CommonRef> {
1515
let mut import = self.r#return.import.clone();
1616
for arg in &self.args {
1717
import.extend(arg.import().into_iter());

pyo3-stub-gen/src/generate/member.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub struct MemberDef {
1313
}
1414

1515
impl Import for MemberDef {
16-
fn import(&self) -> HashSet<ModuleRef> {
16+
fn import(&self) -> HashSet<CommonRef> {
1717
self.r#type.import.clone()
1818
}
1919
}

pyo3-stub-gen/src/generate/method.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub struct MethodDef {
1414
}
1515

1616
impl Import for MethodDef {
17-
fn import(&self) -> HashSet<ModuleRef> {
17+
fn import(&self) -> HashSet<CommonRef> {
1818
let mut import = self.r#return.import.clone();
1919
for arg in &self.args {
2020
import.extend(arg.import().into_iter());

pyo3-stub-gen/src/generate/module.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct Module {
2121
}
2222

2323
impl Import for Module {
24-
fn import(&self) -> HashSet<ModuleRef> {
24+
fn import(&self) -> HashSet<CommonRef> {
2525
let mut imports = HashSet::new();
2626
for class in self.class.values() {
2727
imports.extend(class.import());
@@ -38,12 +38,36 @@ impl fmt::Display for Module {
3838
writeln!(f, "# This file is automatically generated by pyo3_stub_gen")?;
3939
writeln!(f, "# ruff: noqa: E501, F401")?;
4040
writeln!(f)?;
41-
for import in self.import().into_iter().sorted() {
42-
let name = import.get().unwrap_or(&self.default_module_name);
43-
if name != self.name {
44-
writeln!(f, "import {}", name)?;
41+
let package_name = self.default_module_name.split('.').next().unwrap();
42+
let mut type_ref_grouped: BTreeMap<String, Vec<String>> = BTreeMap::new();
43+
for common_ref in self.import().into_iter().sorted() {
44+
match common_ref {
45+
CommonRef::Module(module_ref) => {
46+
let name = module_ref.get().unwrap_or(&self.default_module_name);
47+
if name != self.name {
48+
writeln!(f, "import {}", name)?;
49+
}
50+
}
51+
CommonRef::Type(mut type_ref) => {
52+
if type_ref.module.is_empty() {
53+
type_ref.module = self.default_module_name.clone();
54+
}
55+
if type_ref.module != self.name {
56+
if type_ref.module.starts_with(package_name) {
57+
type_ref_grouped
58+
.entry(type_ref.module)
59+
.or_default()
60+
.push(type_ref.name);
61+
} else {
62+
writeln!(f, "import {}", type_ref.module)?;
63+
}
64+
}
65+
}
4566
}
4667
}
68+
for (module_name, type_names) in type_ref_grouped {
69+
writeln!(f, "from {} import {}", module_name, type_names.join(", "))?;
70+
}
4771
for submod in &self.submodules {
4872
writeln!(f, "from . import {}", submod)?;
4973
}

pyo3-stub-gen/src/generate/stub_info.rs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,32 @@ use std::{
66
io::Write,
77
path::*,
88
};
9-
9+
fn get_destination_paths(module: &Module, python_root: &Path, default_module: &str) -> (PathBuf, Option<PathBuf>) {
10+
let path = module.name.replace(".", "/");
11+
if module.submodules.is_empty() {
12+
if module.name != default_module {
13+
(
14+
python_root.join(format!("{path}.pyi")),
15+
Some(python_root.join(format!("{path}.py"))),
16+
)
17+
} else {
18+
(
19+
python_root.join(format!("{path}.pyi")),
20+
None,
21+
)
22+
}
23+
} else {
24+
(
25+
python_root.join(path).join("__init__.pyi"),
26+
None,
27+
)
28+
}
29+
}
1030
#[derive(Debug, Clone, PartialEq)]
1131
pub struct StubInfo {
1232
pub modules: BTreeMap<String, Module>,
1333
pub python_root: PathBuf,
34+
pub default_module: String
1435
}
1536

1637
impl StubInfo {
@@ -30,13 +51,7 @@ impl StubInfo {
3051

3152
pub fn generate(&self) -> Result<()> {
3253
for (name, module) in self.modules.iter() {
33-
let path = name.replace(".", "/");
34-
let dest = if module.submodules.is_empty() {
35-
self.python_root.join(format!("{path}.pyi"))
36-
} else {
37-
self.python_root.join(path).join("__init__.pyi")
38-
};
39-
54+
let (dest, dest_py) = get_destination_paths(module, &self.python_root, &self.default_module);
4055
let dir = dest.parent().context("Cannot get parent directory")?;
4156
if !dir.exists() {
4257
fs::create_dir_all(dir)?;
@@ -48,6 +63,15 @@ impl StubInfo {
4863
"Generate stub file of a module `{name}` at {dest}",
4964
dest = dest.display()
5065
);
66+
67+
if let Some(dest_py) = dest_py {
68+
let mut f_py = fs::File::create(&dest_py)?;
69+
write!(f_py, "# Fixed pylance reportMissingModuleSource warning when using \n# \"from {} import xxx \" and \"import {}\"", module.name, module.name)?;
70+
log::info!(
71+
"Generate python file of a (not main)module `{name}` at {dest}",
72+
dest = dest_py.display()
73+
);
74+
}
5175
}
5276
Ok(())
5377
}
@@ -189,6 +213,7 @@ impl StubInfoBuilder {
189213
StubInfo {
190214
modules: self.modules,
191215
python_root: self.python_root,
216+
default_module: self.default_module_name
192217
}
193218
}
194219
}

pyo3-stub-gen/src/stub_type.rs

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,37 @@ mod numpy;
77

88
use maplit::hashset;
99
use std::{collections::HashSet, fmt, ops};
10+
use std::cmp::Ordering;
11+
12+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13+
pub enum CommonRef {
14+
Module(ModuleRef),
15+
Type(TypeRef),
16+
}
17+
18+
impl PartialOrd for CommonRef {
19+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
20+
Some(self.cmp(other))
21+
}
22+
}
23+
24+
25+
impl Ord for CommonRef {
26+
fn cmp(&self, other: &Self) -> Ordering {
27+
match (self, other) {
28+
(CommonRef::Module(a), CommonRef::Module(b)) => a.get().cmp(&b.get()),
29+
(CommonRef::Type(a), CommonRef::Type(b)) => a.cmp(b),
30+
(CommonRef::Module(_), CommonRef::Type(_)) => Ordering::Greater,
31+
(CommonRef::Type(_), CommonRef::Module(_)) => Ordering::Less,
32+
}
33+
}
34+
}
35+
36+
impl From<&str> for CommonRef {
37+
fn from(s: &str) -> Self {
38+
CommonRef::Module(s.into())
39+
}
40+
}
1041

1142
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
1243
pub enum ModuleRef {
@@ -39,6 +70,22 @@ impl From<&str> for ModuleRef {
3970
}
4071
}
4172

73+
74+
/// Indicates the dependent type(eg class enum).
75+
/// from module import type.
76+
/// name, type name. module, module name(which type defined).
77+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
78+
pub struct TypeRef {
79+
pub module: String,
80+
pub name: String,
81+
}
82+
83+
impl TypeRef {
84+
pub fn new(module: String, name: String) -> Self {
85+
Self{name, module}
86+
}
87+
}
88+
4289
/// Type information for creating Python stub files annotated by [PyStubType] trait.
4390
#[derive(Debug, Clone, PartialEq, Eq)]
4491
pub struct TypeInfo {
@@ -49,7 +96,7 @@ pub struct TypeInfo {
4996
///
5097
/// For example, when `name` is `typing.Sequence[int]`, `import` should contain `typing`.
5198
/// This makes it possible to use user-defined types in the stub file.
52-
pub import: HashSet<ModuleRef>,
99+
pub import: HashSet<CommonRef>,
53100
}
54101

55102
impl fmt::Display for TypeInfo {
@@ -138,12 +185,35 @@ impl TypeInfo {
138185
/// ```
139186
pub fn with_module(name: &str, module: ModuleRef) -> Self {
140187
let mut import = HashSet::new();
141-
import.insert(module);
188+
import.insert(CommonRef::Module(module));
142189
Self {
143190
name: name.to_string(),
144191
import,
145192
}
146193
}
194+
195+
/// A type annotation of a type that must be imported.
196+
///
197+
/// ```
198+
/// ClassA defined in ModuleA
199+
/// pyo3_stub_gen::TypeInfo::with_type("ClassA", "ModuleA");
200+
/// ```
201+
pub fn with_type(type_name: &str, module: ModuleRef) -> Self {
202+
let mut import = HashSet::new();
203+
let mut module_name = String::new();
204+
match module.get() {
205+
Some(value) => module_name = value.to_string(),
206+
None => module_name = "".to_string(),
207+
}
208+
209+
let type_ref = TypeRef::new(module_name, type_name.to_string());
210+
import.insert(CommonRef::Type(type_ref));
211+
212+
Self {
213+
name: type_name.to_string(),
214+
import,
215+
}
216+
}
147217
}
148218

149219
impl ops::BitOr for TypeInfo {
@@ -244,7 +314,7 @@ mod test {
244314
#[test_case(HashMap::<u32, Vec<u32>>::type_output(), "builtins.dict[builtins.int, builtins.list[builtins.int]]", hashset! { "builtins".into() } ; "HashMap_u32_Vec_u32_output")]
245315
#[test_case(HashSet::<u32>::type_input(), "builtins.set[builtins.int]", hashset! { "builtins".into() } ; "HashSet_u32_input")]
246316
#[test_case(indexmap::IndexSet::<u32>::type_input(), "builtins.set[builtins.int]", hashset! { "builtins".into() } ; "IndexSet_u32_input")]
247-
fn test(tinfo: TypeInfo, name: &str, import: HashSet<ModuleRef>) {
317+
fn test(tinfo: TypeInfo, name: &str, import: HashSet<CommonRef>) {
248318
assert_eq!(tinfo.name, name);
249319
if import.is_empty() {
250320
assert!(tinfo.import.is_empty());

0 commit comments

Comments
 (0)