GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Replace runtime type-name checks with type identity by youknowdot · Pull Request #9723 · RustPython/RustPython

Replace runtime type-name checks with type identity #9723

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
youknowone merged 7 commits into RustPython:main from youknowdot:actual-type-identity-20261105
Nov 6, 2026

Uh oh!

There was an error while loading. Please reload this page.

Merged

Replace runtime type-name checks with type identity #9723

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
7 commits
Select commit Hold shift + click to select a range
7c9de68
Replace runtime type-name checks with type identity
youknowdot Nov 5, 2026
2ece3ac
Match CPython ctypes metadata and finalizer semantics
youknowdot Nov 5, 2026
a68fe9c
Complete ctypes restype deletion and Windows error handling
youknowdot Nov 5, 2026
9000469
Use class-local cached ctypes callback metadata
youknowdot Nov 5, 2026
e5dea97
Use cached ctypes argtypes for ordinary function calls
youknowdot Nov 5, 2026
5f5835b
Use public BaseException export in Windows COM initialization
youknowdot Nov 5, 2026
803a9cb
Preserve COMError initialization in Python subclasses
codex Nov 6, 2026
File filter
Filter by extension

Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view

There are no files selected for viewing

1 change: 0 additions & 1 deletion 1 Lib/test/test_ctypes/test_win32_com_foreign_func.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ class IPersist(IUnknown):

self.assertEqual(0, ppst.Release())

@unittest.expectedFailure # TODO: RUSTPYTHON; - COM iid parameter handling not implemented
def test_with_paramflags_and_iid(self):
class IUnknown(c_void_p):
QueryInterface = proto_query_interface(None, IID_IUnknown)
Expand Down
160 changes: 159 additions & 1 deletion 160 crates/host_env/src/ctypes.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -1835,7 +1835,11 @@ pub const HRESULT_S_OK: i32 = crate::windows::HRESULT_S_OK;

#[cfg(windows)]
pub fn format_error_message(code: Option<u32>) -> Option<String> {
crate::windows::format_error_message(code)
let code = code
.filter(|&code| code != 0)
.unwrap_or_else(crate::windows::get_last_error);
crate::windows::format_error_message_with_language(code, 1 << 10)
.map(|message| message.trim_end().to_owned())
}

#[cfg(windows)]
Expand Down Expand Up @@ -3860,3 +3864,157 @@ mod tests {
}
}
}

/// Owned UTF-16 fields returned by COM's error-information interfaces.
#[cfg(windows)]
#[derive(Default)]
pub struct ComErrorDetails {
pub description: Option<Vec<u16>>,
pub source: Option<Vec<u16>>,
pub helpfile: Option<Vec<u16>>,
pub help_context: u32,
pub progid: Option<Vec<u16>>,
}

/// Format a COM error like ctypes' FormatError (retain trailing punctuation).
#[cfg(windows)]
pub fn format_com_error_message(status: i32) -> Option<String> {
crate::windows::format_error_message_with_language(status as u32, 1 << 10)
.map(|message| message.trim_end().to_owned())
}

/// Obtain the error information associated with a failed COM method call.
///
/// The caller must detach its interpreter while this runs: COM methods can
/// call back into Python. No borrowed Python buffers may be held here.
///
/// # Safety
/// `com_ptr` must identify a live IUnknown-compatible COM interface for the
/// duration of this call. `iid` is the native byte representation of its GUID.
#[cfg(windows)]
pub unsafe fn com_error_details(com_ptr: usize, iid: &[u8; 16]) -> ComErrorDetails {
use windows_sys::core::GUID;

type QueryInterface =
unsafe extern "system" fn(*mut c_void, *const GUID, *mut *mut c_void) -> i32;
type Release = unsafe extern "system" fn(*mut c_void) -> u32;
type SupportsErrorInfo = unsafe extern "system" fn(*mut c_void, *const GUID) -> i32;
type GetGuid = unsafe extern "system" fn(*mut c_void, *mut GUID) -> i32;
type GetString = unsafe extern "system" fn(*mut c_void, *mut *mut u16) -> i32;
type GetContext = unsafe extern "system" fn(*mut c_void, *mut u32) -> i32;

struct ComRef(*mut c_void);
impl Drop for ComRef {
fn drop(&mut self) {
// SAFETY: ComRef owns one reference on an IUnknown-compatible
// interface, whose third vtable entry is Release.
unsafe {
let release: Release = core::mem::transmute(method(self.0, 2));
release(self.0);
}
}
}

unsafe fn method(interface: *mut c_void, index: usize) -> usize {
// SAFETY: the caller provides a live interface and a valid slot for
// that interface's documented COM ABI.
unsafe { *(*(interface as *const *const usize)).add(index) }
}

unsafe fn string_field(interface: *mut c_void, index: usize) -> Option<Vec<u16>> {
let mut value = core::ptr::null_mut();
// SAFETY: IErrorInfo slots 4–6 all return an owned BSTR through an
// initialized output pointer. Free even if the method reports failure.
unsafe {
let get: GetString = core::mem::transmute(method(interface, index));
get(interface, &mut value);
if value.is_null() {
return None;
}
let units = core::slice::from_raw_parts(value, sys_string_len(value));
// CPython builds these fields from NUL-terminated wide strings.
let end = units
.iter()
.position(|&unit| unit == 0)
.unwrap_or(units.len());
let result = units[..end].to_vec();
sys_free_string(value);
Some(result)
}
}

let mut details = ComErrorDetails::default();
if com_ptr == 0 {
return details;
}
// IID_ISupportErrorInfo = {DF0B3D60-548F-101B-8E65-08002B2BD119}.
let support_iid = GUID {
data1: 0xdf0b3d60,
data2: 0x548f,
data3: 0x101b,
data4: [0x8e, 0x65, 0x08, 0x00, 0x2b, 0x2b, 0xd1, 0x19],
};
// SAFETY: GUID is a 16-byte POD value; the input need not be aligned.
let iid = unsafe { core::ptr::read_unaligned(iid.as_ptr().cast::<GUID>()) };
let interface = com_ptr as *mut c_void;
let mut support = core::ptr::null_mut();
// SAFETY: the live input interface implements IUnknown. A successful
// QueryInterface returns one owned ISupportErrorInfo reference.
let status = unsafe {
let query: QueryInterface = core::mem::transmute(method(interface, 0));
query(interface, &support_iid, &mut support)
};
if status < 0 || support.is_null() {
return details;
}
let support = ComRef(support);
// SAFETY: ISupportErrorInfo's fourth slot accepts the requested IID.
let status = unsafe {
let supports: SupportsErrorInfo = core::mem::transmute(method(support.0, 3));
supports(support.0, &iid)
};
drop(support);
if status < 0 {
return details;
}
let Some(error_info) = get_error_info() else {
return details;
};
if error_info.is_null() {
return details;
}
let error_info = ComRef(error_info);
let mut guid = GUID {
data1: 0,
data2: 0,
data3: 0,
data4: [0; 8],
};
// SAFETY: GetErrorInfo supplied a live IErrorInfo. Its slots have the
// documented signatures, and all output storage is initialized.
let guid_status = unsafe {
details.description = string_field(error_info.0, 5);
let get_guid: GetGuid = core::mem::transmute(method(error_info.0, 3));
let status = get_guid(error_info.0, &mut guid);
let get_context: GetContext = core::mem::transmute(method(error_info.0, 7));
get_context(error_info.0, &mut details.help_context);
details.helpfile = string_field(error_info.0, 6);
details.source = string_field(error_info.0, 4);
status
};
drop(error_info);
if guid_status >= 0 {
// SAFETY: GUID is POD; copy the initialized native representation.
let guid_bytes =
unsafe { core::ptr::read_unaligned((&guid as *const GUID).cast::<[u8; 16]>()) };
if let Some(progid) = prog_id_from_clsid(&guid_bytes) {
// SAFETY: ProgIDFromCLSID returns a NUL-terminated allocation
// owned by this call and released with CoTaskMemFree.
unsafe {
details.progid = Some(WideCStr::from_ptr_str(progid).as_slice().to_vec());
co_task_mem_free(progid.cast());
}
}
}
details
}
2 changes: 1 addition & 1 deletion 2 crates/vm/src/builtins/set.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -1316,7 +1316,7 @@ impl Representable for PySet {
}

if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) {
let name = (class_name != "set").then_some(class_name);
let name = (!class.is(vm.ctx.types.set_type)).then_some(class_name);
zelf.inner.repr(name, vm)
} else {
Ok(Wtf8Buf::from(format!("{class_name}(...)")))
Expand Down
2 changes: 1 addition & 1 deletion 2 crates/vm/src/exceptions.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -2631,7 +2631,7 @@ pub(super) mod types {
// We need this method, because of how `CPython` copies `init`
// from `BaseException` in `SimpleExtendsException` macro.
// See: `BaseException_new`
if *cls.name() == *vm.ctx.exceptions.os_error.name() {
if cls.is(vm.ctx.exceptions.os_error) {
let args_vec = args.args.to_vec();
let len = args_vec.len();
if (2..=5).contains(&len) {
Expand Down
115 changes: 46 additions & 69 deletions 115 crates/vm/src/stdlib/_ctypes.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ pub(crate) mod _ctypes {
use crate::convert::ToPyObject;
use crate::function::{ArgStrictInt, Either, OptionalArg};
use crate::types::Representable;
#[cfg(windows)]
use crate::types::{Constructor, Initializer};
use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine};
use num_traits::ToPrimitive;

Expand Down Expand Up @@ -294,76 +296,49 @@ pub(crate) mod _ctypes {
)
}

#[cfg(target_os = "windows")]
#[pyattr(name = "COMError", once)]
fn com_error(vm: &VirtualMachine) -> PyTypeRef {
use crate::PyObject;
use crate::builtins::type_::PyAttributes;
use crate::function::FuncArgs;
use crate::types::{PyTypeFlags, PyTypeSlots};

// Sets hresult, text, details as instance attributes in __init__
// This function has InitFunc signature for direct slots.init use
fn comerror_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
let (hresult, text, details): (
Option<PyObjectRef>,
Option<PyObjectRef>,
Option<PyObjectRef>,
) = args.bind(vm)?;
let hresult = hresult.unwrap_or_else(|| vm.ctx.none());
let text = text.unwrap_or_else(|| vm.ctx.none());
let details = details.unwrap_or_else(|| vm.ctx.none());

// Set instance attributes
zelf.set_attr("hresult", hresult, vm)?;
zelf.set_attr("text", text.clone(), vm)?;
zelf.set_attr("details", details.clone(), vm)?;
/// Raised when a COM method call failed.
#[cfg(windows)]
#[pyattr]
#[pyexception(name = "COMError", module = "_ctypes", base = crate::builtins::PyException)]
#[derive(Debug)]
#[repr(transparent)]
struct PyComError(crate::builtins::PyException);

// self.args = args[1:] = (text, details)
// via: PyObject_SetAttrString(self, "args", PySequence_GetSlice(args, 1, size))
let args_tuple: PyObjectRef = vm.ctx.new_tuple(vec![text, details]).into();
zelf.set_attr("args", args_tuple, vm)?;
#[cfg(windows)]
#[pyexception(with(Initializer), flags(HEAPTYPE))]
impl PyComError {}

#[cfg(windows)]
impl Initializer for PyComError {
type Args = crate::function::FuncArgs;

fn slot_init(
zelf: &crate::PyObject,
args: Self::Args,
vm: &VirtualMachine,
) -> PyResult<()> {
let (hresult, text, details): (PyObjectRef, PyObjectRef, PyObjectRef) =
args.bind(vm)?;
let full_args = vec![hresult.clone(), text.clone(), details.clone()];
// Match the observable attribute assignments before restoring the
// base payload directly, without invoking an overriding descriptor.
let short_args: PyObjectRef =
vm.ctx.new_tuple(vec![text.clone(), details.clone()]).into();
zelf.set_attr("args", short_args, vm)?;
zelf.set_attr("hresult", hresult, vm)?;
zelf.set_attr("text", text, vm)?;
zelf.set_attr("details", details, vm)?;
crate::builtins::PyBaseException::slot_init(
zelf,
crate::function::FuncArgs::from(full_args),
vm,
)?;
Ok(())
}

// Create exception type with IMMUTABLETYPE flag
let mut attrs = PyAttributes::default();
attrs.insert(
vm.ctx.intern_str("__module__"),
vm.ctx.new_str("_ctypes").into(),
);
attrs.insert(
vm.ctx.intern_str("__doc__"),
vm.ctx
.new_str("Raised when a COM method call failed.")
.into(),
);

// Create slots with IMMUTABLETYPE flag
let slots = PyTypeSlots {
name: "COMError",
flags: crate::types::AtomicPyTypeFlags::from_plain(
PyTypeFlags::HEAP_TYPE_DICT_IMMUTABLE,
),
..PyTypeSlots::default()
};

let exc_type = PyType::new_heap(
"COMError",
vec![vm.ctx.exceptions.exception_type.to_owned()],
attrs,
slots,
vm.ctx.types.type_type.to_owned(),
&vm.ctx,
)
.unwrap();

// Set our custom init after new_heap, which runs init_slots that would
// otherwise overwrite slots.init with init_wrapper (due to __init__ in MRO).
exc_type.slots.init.store(Some(comerror_init));

exc_type
fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
unreachable!("slot_init is defined")
}
}

#[pyfunction]
Expand Down Expand Up @@ -608,7 +583,7 @@ pub(crate) mod _ctypes {

#[cfg(target_os = "windows")]
#[pyfunction(name = "_check_HRESULT")]
fn check_hresult(hr: i32, vm: &VirtualMachine) -> PyResult<i32> {
pub(super) fn check_hresult(hr: i32, vm: &VirtualMachine) -> PyResult<i32> {
if hr < 0 {
let errno = crate::host_env::os::winerror_to_errno(hr);
let message = crate::host_env::uuid::format_error_message(hr);
Expand Down Expand Up @@ -1042,9 +1017,11 @@ pub(crate) mod _ctypes {

#[cfg(windows)]
#[pyfunction(name = "FormatError")]
fn format_error_func(code: OptionalArg<u32>, _vm: &VirtualMachine) -> String {
rustpython_host_env::ctypes::format_error_message(code.into_option())
.unwrap_or_else(|| "<no description>".to_string())
fn format_error_func(code: OptionalArg<i32>, _vm: &VirtualMachine) -> String {
rustpython_host_env::ctypes::format_error_message(
code.into_option().map(|code| code as u32),
)
.unwrap_or_else(|| "<no description>".to_string())
}

#[cfg(windows)]
Expand Down
Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.