Repository navigation
Support VT_ERROR variant type. - #965
Merged
Merged
Conversation
…shalling tests. - Introduce a C++ native COM test double for creating and reading `VT_ERROR` `VARIANT`s. - Ensure test double validates error variant marshalling across Python and COM boundaries.
- Added early return for VT_ERROR in `tagVARIANT.__ctypes_from_outparam__`. - So `scode` is inline value, no extra free needed by caller. - Added test suite `test_variant_error` covering error variant behavior.
- Added `scode` field to `tagVARIANT` (`U_VARIANT2`) to expose error codes directly. - The addition of `scode` does not change the size or memory layout of `tagVARIANT` because it occupies the existing union slot, preserving binary layout. - No backward‑compatible breaking changes; existing code continues to work unchanged.
The relevant `VARIANT` layout uses a `U_VARIANT2(Union)` in which
`("VT_I4", c_long)` and `("scode", SCODE)` share the same 32-bit storage.
In the cached diff, the code first sets `v.vt = VT_ERROR` to declare that
this slot is being used as a `VT_ERROR` variant, and then writes `0x80020004`
into the underlying field.
This value is `0x80020004` in the raw bit pattern, which is the HRESULT bit
pattern for `DISP_E_PARAMNOTFOUND`. In Python, that pattern is represented as
`2147549188`, but when read as a signed 32-bit integer it becomes
`-2147352572`, which is exactly `hresult.DISP_E_PARAMNOTFOUND`.
The key point is that the same 32-bit storage is interpreted differently
depending on the active `vt` tag: when the tag is `VT_ERROR`, the value is
read back as `scode`. Therefore, the real behavior is:
- `v.vt = VT_ERROR` declares that this `VARIANT` is an error value.
- `v._.VT_I4 = 0x80020004` writes the HRESULT bit pattern into the shared
union storage.
- `v.value` reads that same storage as `scode`.
- `v.value == hresult.DISP_E_PARAMNOTFOUND` is therefore true.
This means the existing code was not wrong to assign the value to
`v._.VT_I4`. The issue discussion was based on a misunderstanding of how
`VARIANT` stores `VT_ERROR` values, not on a real defect in the code itself.
Make `VARIANT._get_value()` return the original `VARIANT` for `VT_ERROR`. Do not turn its `scode` into a plain integer. Excel returns `=1/0` as an error cell with `VT_ERROR` and an HRESULT. If `Range.Value()` returns only `-2146826281`, users cannot tell an error code from a real integer. Now `Range.Value()` returns `VARIANT(vt=0xa, -2146826281)`, and `WorksheetFunction.IsError()` returns `True`. The negative number is the signed form of COM HRESULT `0x800A07D7`. It is the error value for Excel `#DIV/0!`. This fixes the old `NotImplementedError` while keeping the error type. Remove the old `VT_ERROR` branch in `__ctypes_from_outparam__()`. `scode` is an inline value, not a pointer, so returning the `VARIANT` keeps the error data. No special resource cleanup is needed. Update the `VARIANT.missing` checks to use `._.scode`, not `.value`. Also update the regression tests to check the error value and the object.
Add an Excel test for a cell with the formula `=1/0`. Excel should return a `VARIANT` with `VT_ERROR`, not a plain integer. This test checks that `Range.Value()` keeps the error type. It also checks that its `scode` is HRESULT `0x800A07D7`. That HRESULT is the signed error value for Excel `#DIV/0!`. The test protects the new behavior from future changes.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #965 +/- ##
=======================================
Coverage 89.21% 89.21%
=======================================
Files 142 143 +1
Lines 13870 13933 +63
=======================================
+ Hits 12374 12431 +57
- Misses 1496 1502 +6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Ensure unsupported `VARIANT` types retain their explicit `NotImplementedError` behavior.
junkmd
force-pushed
the
support_variant_error
branch
from
October 12, 2026 03:40
5865121 to
121f60b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
This PR fixes how
VT_ERRORvalues are returned from aVARIANT.Closes #821
Related to #325
What changed
A
VT_ERRORvalue contains an error code in itsscodefield.The old code did not handle
VT_ERRORin_get_value(). Therefore, reading an Excel error cell raised:This PR now returns the original
VARIANTobject forVT_ERROR.For example:
Why
_get_value()does not returnself._.scodeself._.scodecontains the error code, but it does not contain the full meaning of the value.If
_get_value()returns onlyself._.scode, thediv0err_rng.Value()result becomes-2146826281, a normal Python integer.The caller can no longer tell whether this integer is a normal integer or an error value from a
VT_ERRORVARIANT.Returning
selfkeeps both parts:vt=VT_ERRORscode=-2146826281This is important because COM users and Excel need to know that the value is an error.
It also allows
WorksheetFunction.IsError()to returnTrue.self._.scodeis still used when the code needs to inspect the actual error code. It is not used as the return value because returning it would remove theVARIANTtype information.This commit log records the history of an attempt to make the
valueproperty return error codes, which caused compatibility issues with Excel and led to the current design.Please refer to that log for technical implementation details.
Excel example
For the
=1/0formula, Excel returns aVT_ERRORvalue.The error code is
-2146826281.As an unsigned hexadecimal value, this is:
This is the COM error code for Excel
#DIV/0!.Excel builds this
HRESULTfrom the Excel error number.The Excel error number for
#DIV/0!is 2007, or0x07D7in hexadecimal.https://learn.microsoft.com/en-us/office/vba/api/excel.xlcverror
Excel adds the Excel facility value
0x800A0000, so the result is0x800A07D7.Impact on existing users
Code that catches
NotImplementedErrorfromrng.Value()may need to change.The call no longer raises that error for
VT_ERROR.The caller should check the returned
VARIANTinstead:Alternatively, use a COM server-side utility like
IsError:This keeps the error type and the error code available to the caller.
Test without Excel
The Excel test checks the real Excel behavior, but Excel cannot be used in the CI pipeline.
To address this, the test suite also uses the native C++ COM test server in
source/CppTestSrv.It returns
VT_ERRORvalues and reads theirscodevalues through a real COM call.The CI job builds and registers this local server with MSVC before running the tests.
This is a native COM test double for the Excel part of this change.
It runs in CI without installing or starting Excel, so the test checks the
VARIANTbehavior on its own.Tests
The tests check that:
VT_ERRORvalues can be read without raisingNotImplementedErrorVARIANTVARIANTkeepsVT_ERRORscodevalue is preservedVARIANTWorksheetFunction.IsError()returnsTrue