Skip to content

Commit 7819d06

Browse files
authored
feat: Format function names with qualname for Python 3.11+ (#825)
This change will update function names to have module paths in them which is useful for disambiguating functions when viewing a profile. See https://docs.python.org/3/glossary.html#term-qualified-name
1 parent d230d82 commit 7819d06

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

src/python_interpreters.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ pub trait CodeObject: Copy {
7070
fn varnames(&self) -> *mut Self::TupleObject;
7171

7272
fn get_line_number(&self, lasti: i32, table: &[u8]) -> i32;
73+
74+
/// Returns the qualified name (co_qualname) if available (Python 3.11+).
75+
/// For older Python versions, returns None.
76+
fn qualname(&self) -> Option<*mut Self::StringObject> {
77+
None
78+
}
7379
}
7480

7581
pub trait BytesObject: Copy {
@@ -354,6 +360,10 @@ macro_rules! CompactCodeObjectImpl {
354360
}
355361
line_number
356362
}
363+
364+
fn qualname(&self) -> Option<*mut Self::StringObject> {
365+
Some(self.co_qualname as *mut Self::StringObject)
366+
}
357367
}
358368
};
359369
}
@@ -921,4 +931,24 @@ mod tests {
921931
];
922932
assert_eq!(code.get_line_number(214, &table), 5);
923933
}
934+
935+
#[test]
936+
fn test_py3_11_qualname() {
937+
use crate::python_bindings::v3_11_0::PyCodeObject;
938+
let code = PyCodeObject {
939+
co_firstlineno: 1,
940+
..Default::default()
941+
};
942+
assert!(code.qualname().is_some());
943+
}
944+
945+
#[test]
946+
fn test_py3_10_qualname_not_available() {
947+
use crate::python_bindings::v3_10_0::PyCodeObject;
948+
let code = PyCodeObject {
949+
co_firstlineno: 1,
950+
..Default::default()
951+
};
952+
assert!(code.qualname().is_none());
953+
}
924954
}

src/stack_trace.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,15 @@ where
150150
.context("Failed to copy PyCodeObject")?;
151151

152152
let filename = copy_string(code.filename(), process).context("Failed to copy filename");
153-
let name = copy_string(code.name(), process).context("Failed to copy function name");
153+
154+
// Try to get qualname first (available in Python 3.11+), fall back to name
155+
let name = match code.qualname() {
156+
Some(qualname_ptr) => {
157+
copy_string(qualname_ptr, process).or_else(|_| copy_string(code.name(), process))
158+
}
159+
None => copy_string(code.name(), process),
160+
}
161+
.context("Failed to copy function name");
154162

155163
// just skip processing the current frame if we can't load the filename or function name.
156164
// this can happen in python 3.13+ since the f_executable isn't guaranteed to be

0 commit comments

Comments
 (0)