Skip to content

Update PyO3 to 0.29 & enable c-api tests#8000

Open
bschoenmaeckers wants to merge 7 commits into
RustPython:mainfrom
bschoenmaeckers:c-api-misc-tests
Open

Update PyO3 to 0.29 & enable c-api tests#8000
bschoenmaeckers wants to merge 7 commits into
RustPython:mainfrom
bschoenmaeckers:c-api-misc-tests

Conversation

@bschoenmaeckers

@bschoenmaeckers bschoenmaeckers commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added C-API capabilities for setting exception cause/context/traceback, reference-count helpers, importing/executing modules, dict “get-or-insert default”, generic aliases, os.fspath, critical-section stubs, interpreter state access, and integer masking utilities.
  • Tests
    • Enabled previously disabled Rust unit tests across the C-API surface so they run in standard test builds.
  • Chores
    • Switched Python backend to RustPython, updated configured Python version to 3.15, and adjusted ABI settings accordingly.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b5528483-f298-477b-ac06-b11f33bdfc6c

📥 Commits

Reviewing files that changed from the base of the PR and between 2ff30a0 and 1c0fc55.

📒 Files selected for processing (40)
  • .cspell.dict/cpython.txt
  • .github/workflows/ci.yaml
  • .github/workflows/update-caches.yml
  • crates/capi/Cargo.toml
  • crates/capi/pyo3-rustpython.config
  • crates/capi/src/abstract_.rs
  • crates/capi/src/abstract_/iter.rs
  • crates/capi/src/abstract_/mapping.rs
  • crates/capi/src/abstract_/number.rs
  • crates/capi/src/abstract_/sequence.rs
  • crates/capi/src/bytearrayobject.rs
  • crates/capi/src/bytesobject.rs
  • crates/capi/src/ceval.rs
  • crates/capi/src/complexobject.rs
  • crates/capi/src/critical_section.rs
  • crates/capi/src/descrobject.rs
  • crates/capi/src/dictobject.rs
  • crates/capi/src/floatobject.rs
  • crates/capi/src/genericaliasobject.rs
  • crates/capi/src/import.rs
  • crates/capi/src/lib.rs
  • crates/capi/src/listobject.rs
  • crates/capi/src/longobject.rs
  • crates/capi/src/methodobject.rs
  • crates/capi/src/object.rs
  • crates/capi/src/osmodule.rs
  • crates/capi/src/pycapsule.rs
  • crates/capi/src/pyerrors.rs
  • crates/capi/src/pylifecycle.rs
  • crates/capi/src/pystate.rs
  • crates/capi/src/refcount.rs
  • crates/capi/src/setobject.rs
  • crates/capi/src/sliceobject.rs
  • crates/capi/src/tupleobject.rs
  • crates/capi/src/unicodeobject.rs
  • crates/capi/src/util.rs
  • crates/capi/src/warnings.rs
  • crates/capi/src/weakrefobject.rs
  • crates/vm/src/builtins/int.rs
  • crates/vm/src/stdlib/_ctypes/function.rs
 _____________________
< I've git the power! >
 ---------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
📝 Walkthrough

Walkthrough

The PR updates the RustPython C-API crate to use PyO3's abi3t feature targeting RustPython 3.15, registers new modules (critical_section, genericaliasobject, osmodule), adds exported C-API functions across lifecycle, state, numeric, dict, exception, refcount, import, and path domains, handles PyObject-typed ctypes returns, and activates previously disabled test modules throughout.

Changes

RustPython C-API Surface Expansion and Test Activation

Layer / File(s) Summary
PyO3 and build configuration
crates/capi/Cargo.toml, crates/capi/pyo3-rustpython.config, .github/workflows/ci.yaml, .github/workflows/update-caches.yml, .cspell.dict/cpython.txt
Switches pyo3 from abi3 to abi3t, changes config backend to RustPython with target_abi=RustPython-abi3t-3.15 and version 3.15, and adds synchronization comments between CI workflow files.
Interpreter state and version exports
crates/capi/src/pystate.rs, crates/capi/src/pylifecycle.rs, crates/capi/src/util.rs
Aliases PyInterpreterState to Interpreter, adds typed interp field to PyThreadState, exports PyInterpreterState_Get/PyInterpreterState_GetID, exports Py_Version global and Py_GetVersion() with lazy version string, and adds Windows FfiResult impl for i64.
New modules and import helpers
crates/capi/src/lib.rs, crates/capi/src/critical_section.rs, crates/capi/src/genericaliasobject.rs, crates/capi/src/osmodule.rs, crates/capi/src/import.rs
Registers three new public modules, adds no-op critical-section structs and begin/end stubs, implements Py_GenericAlias, PyOS_FSPath, PyImport_AddModuleRef, and PyImport_ExecCodeModuleEx.
Numeric conversion and refcount operations
crates/vm/src/builtins/int.rs, crates/capi/src/longobject.rs, crates/capi/src/abstract_/number.rs, crates/capi/src/refcount.rs
Adds PyInt::as_u64_mask helper method for masked 64-bit conversion, exports PyNumber_Long and PyLong_AsUnsignedLongLongMask, exports Py_NewRef and Py_REFCNT for refcount management, with validation tests.
Dict and object operations
crates/capi/src/dictobject.rs, crates/capi/src/object.rs
Adds PyDict_SetDefaultRef for get-or-insert-default semantics with result out-parameter, enables object tests validating PyObject_GenericGetDict.
Exception state mutation
crates/capi/src/pyerrors.rs
Exports PyException_SetCause, PyException_SetContext, and PyException_SetTraceback to mutate exception attributes from C, with null-pointer handling, and updates existing tests to use PyErr::take.
ctypes PyObject return handling
crates/vm/src/stdlib/_ctypes/function.rs
Extends convert_raw_result with _type_=="O" branch that interprets raw FFI returns as PyObject*, validates non-null, and reconstructs PyObjectRef.
Test module activation across capi
crates/capi/src/abstract_/..., crates/capi/src/bytesobject.rs, crates/capi/src/ceval.rs, crates/capi/src/complexobject.rs, crates/capi/src/descrobject.rs, crates/capi/src/floatobject.rs, crates/capi/src/listobject.rs, crates/capi/src/methodobject.rs, crates/capi/src/pycapsule.rs, crates/capi/src/setobject.rs, crates/capi/src/sliceobject.rs, crates/capi/src/tupleobject.rs, crates/capi/src/unicodeobject.rs, crates/capi/src/warnings.rs, crates/capi/src/weakrefobject.rs, crates/capi/src/bytearrayobject.rs
Switches #[cfg(false)] to #[cfg(test)] across deferred test modules, renames test functions removing the test_ prefix, and makes minor fixture adjustments like PyMappingProxy::new parameter passing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • RustPython/RustPython#7929: Both modify crates/capi/src/dictobject.rs to extend the dict C-API — this PR adds PyDict_SetDefaultRef while the retrieved PR adds core dict entrypoints like PyDict_New/PyDict_SetItem.
  • RustPython/RustPython#7915: Both modify crates/capi/src/pyerrors.rs at the exception C-API layer — this PR adds PyException_SetCause/SetContext/SetTraceback while the retrieved PR adds corresponding getter functions.
  • RustPython/RustPython#7881: This PR extends crates/capi/src/import.rs with PyImport_AddModuleRef and PyImport_ExecCodeModuleEx, building on the import surface introduced in the retrieved PR.

Suggested reviewers

  • youknowone

🐇 A rabbit hops through modules wide,
cfg(test) gates flung open wide,
New C-APIs bloom in the crate,
abi3t takes abi3's place,
Exception causes, dicts that default,
Version strings unfurled with a somersault!
🎉 RustPython grows its Python face!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main changes: updating PyO3 to version 0.29 and enabling C-API tests, both of which are central to the PR.
Docstring Coverage ✅ Passed Docstring coverage is 91.58% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can get early access to new features in CodeRabbit.

Enable the early_access setting to enable early access features such as new models, tools, and more.

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[x] lib: cpython/Lib/ctypes
[ ] test: cpython/Lib/test/test_ctypes (TODO: 7)
[x] test: cpython/Lib/test/test_stable_abi_ctypes.py

dependencies:

  • ctypes

dependent tests: (34 tests)

  • ctypes: test_android test_buffer test_bytes test_code test_codecs test_ctypes test_genericalias test_io test_ntpath test_os test_ssl test_venv
    • platform: test__locale test__osx_support test_asyncio test_baseexception test_builtin test_cmath test_fcntl test_math test_mimetypes test_platform test_posix test_regrtest test_shutil test_socket test_strptime test_sysconfig test_time test_winreg test_wsgiref
      • pydoc: test_enum test_pydoc
    • webbrowser: test_webbrowser

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
Cargo.toml (1)

122-122: ⚡ Quick win

Pin PyO3 git rev in Cargo.toml

Cargo.lock already resolves all PyO3 crates (pyo3, pyo3-ffi, pyo3-build-config, pyo3-macros, pyo3-macros-backend) to the same commit (de5816ada27bfa38cb3176d91bd6c93e206dcb3f), but Cargo.toml still uses bare git URLs for both pyo3 and pyo3-ffi. Add rev = "de5816ada27bfa38cb3176d91bd6c93e206dcb3f" to both entries to make the manifest explicitly reproducible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` at line 122, Update the Cargo.toml git dependencies for pyo3 and
pyo3-ffi to pin them to the resolved commit: add rev =
"de5816ada27bfa38cb3176d91bd6c93e206dcb3f" to the pyo3 = { git =
"https://github.com/PyO3/pyo3" } and pyo3-ffi = { git =
"https://github.com/PyO3/pyo3" } entries so the manifest explicitly matches the
commit resolved in Cargo.lock.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/capi/src/import.rs`:
- Around line 24-31: The current logic collapses all sys_modules.get_item
failures into creating a new module; instead, explicitly handle each case: call
sys_modules.get_item(name, vm) and if it returns Err, propagate or return that
error rather than creating a module; if it returns Ok(Some(value)) but
value.downcast_ref::<PyModule>() is None, return a type/mismatch error instead
of overwriting sys.modules; only when get_item returns Ok(None) should you call
vm.new_module(name, vm.ctx.new_dict(), None) and sys_modules.set_item(name,
module.clone().into(), vm)?; update the block around sys_modules.get_item,
downcast_ref::<PyModule>(), vm.new_module, and sys_modules.set_item accordingly.
- Around line 16-18: The extern "C" function PyImport_AddModuleRef currently
calls CStr::from_ptr(name).to_str().expect(...), which can panic on invalid
UTF-8; change it to return an FfiResult error instead of panicking by replacing
the expect with a to_str().map_err(|_| vm.new_system_error("Name is not valid
UTF-8"))? expression (using the existing with_vm/FfiResult flow) so invalid
UTF-8 is converted into a Python SystemError via vm.new_system_error rather than
causing a panic.

In `@crates/capi/src/object.rs`:
- Around line 205-227: PyObject_GenericGetDict and PyObject_GenericSetDict
incorrectly call obj.get_attr / obj.set_attr which go through attribute
machinery and allow __getattribute__/descriptors to intercept; instead wire
these C-API functions to the VM helpers that bypass attribute hooks. Replace the
calls in PyObject_GenericGetDict and PyObject_GenericSetDict to use the direct
helpers (object_get_dict and object_generic_set_dict from
crates/vm/src/builtins/object.rs) that read/write the underlying instance dict
(obj.dict()/obj.instance_dict()) without routing through get_attr/set_attr so
the real instance dict is returned/updated.

In `@crates/capi/src/pyerrors.rs`:
- Around line 303-308: PyException_SetCause currently clones the borrowed raw
pointer (using to_owned()) which violates CPython’s reference-stealing semantics
and leaks/increfs; change it to take ownership of the incoming raw pointer for
`cause` (use the equivalent of a from_raw/from_owned conversion on the raw `*mut
PyObject`) so the caller’s reference is consumed, map NULL to Python None as
before for clearing, and then pass that owned PyBaseException into
`exc.set___cause__` instead of cloning; ensure no extra incref remains and the
raw pointer is not used after ownership is transferred.

In `@crates/capi/src/pylifecycle.rs`:
- Around line 61-63: The function Py_GetVersion currently returns String::as_ptr
which may lack a terminating NUL; change the static LazyLock to hold a CString
(e.g. static VERSION: LazyLock<CString> = LazyLock::new(||
CString::new(format!("{MAJOR}.{MINOR}.{MICRO}")).unwrap());) and return
VERSION.as_ptr() as *const c_char so callers get a stable, NUL-terminated C
string from Py_GetVersion. Ensure you import std::ffi::CString and keep the
LazyLock so the CString lives for the program duration.

---

Nitpick comments:
In `@Cargo.toml`:
- Line 122: Update the Cargo.toml git dependencies for pyo3 and pyo3-ffi to pin
them to the resolved commit: add rev =
"de5816ada27bfa38cb3176d91bd6c93e206dcb3f" to the pyo3 = { git =
"https://github.com/PyO3/pyo3" } and pyo3-ffi = { git =
"https://github.com/PyO3/pyo3" } entries so the manifest explicitly matches the
commit resolved in Cargo.lock.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 4e562741-82e6-4a0e-ad6f-0b276776160f

📥 Commits

Reviewing files that changed from the base of the PR and between 1ce37bf and 43529c0.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • Lib/test/test_ctypes/test_python_api.py is excluded by !Lib/**
📒 Files selected for processing (22)
  • Cargo.toml
  • crates/capi/pyo3-rustpython.config
  • crates/capi/src/abstract_.rs
  • crates/capi/src/bytesobject.rs
  • crates/capi/src/ceval.rs
  • crates/capi/src/complexobject.rs
  • crates/capi/src/dictobject.rs
  • crates/capi/src/floatobject.rs
  • crates/capi/src/import.rs
  • crates/capi/src/lib.rs
  • crates/capi/src/listobject.rs
  • crates/capi/src/longobject.rs
  • crates/capi/src/methodobject.rs
  • crates/capi/src/moduleobject.rs
  • crates/capi/src/object.rs
  • crates/capi/src/pycapsule.rs
  • crates/capi/src/pyerrors.rs
  • crates/capi/src/pylifecycle.rs
  • crates/capi/src/refcount.rs
  • crates/capi/src/tupleobject.rs
  • crates/capi/src/unicodeobject.rs
  • crates/vm/src/stdlib/builtins.rs

Comment thread crates/capi/src/import.rs Outdated
Comment thread crates/capi/src/import.rs Outdated
Comment thread crates/capi/src/object.rs
Comment thread crates/capi/src/pyerrors.rs
Comment thread crates/capi/src/pylifecycle.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/vm/src/stdlib/_ctypes/function.rs`:
- Around line 1435-1455: The code incorrectly maps a NULL py_object to
Some(None) and also leaks a refcount by using ManuallyDrop + clone; update the
convert_raw_result branch that handles restype_type "_type_" == "O" so that when
ptr == 0 you raise a ValueError("PyObject is NULL") on the vm and return None
(propagating an error), and when ptr != 0 construct the PyObjectRef by moving
ownership with PyObjectRef::from_raw(NonNull::new_unchecked(ptr as *mut
PyObject)) and return Some(obj) directly (do NOT wrap in ManuallyDrop or clone),
referencing the restype_type check, RawResult variants, and
PyObjectRef::from_raw to locate and fix the code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: bd14ff21-a37e-48fe-bff9-21046b4badbd

📥 Commits

Reviewing files that changed from the base of the PR and between 2149dcb and 3f5cfe3.

📒 Files selected for processing (2)
  • .github/workflows/ci.yaml
  • crates/vm/src/stdlib/_ctypes/function.rs

Comment thread crates/vm/src/stdlib/_ctypes/function.rs
@bschoenmaeckers

Copy link
Copy Markdown
Contributor Author

CI failures seem unrelated?

@ShaharNaveh ShaharNaveh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TYSM for all the work on this!

just a minor request:)

Comment thread .github/workflows/ci.yaml Outdated

@ShaharNaveh ShaharNaveh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TYSM!

@bschoenmaeckers bschoenmaeckers force-pushed the c-api-misc-tests branch 3 times, most recently from 26b96e3 to e77a566 Compare June 3, 2026 09:46
@bschoenmaeckers bschoenmaeckers marked this pull request as draft June 3, 2026 12:08
@bschoenmaeckers bschoenmaeckers force-pushed the c-api-misc-tests branch 8 times, most recently from 25a2860 to b8b775a Compare June 10, 2026 07:42
@bschoenmaeckers bschoenmaeckers changed the title Use PyO3 main & enable c-api tests Update PyO3 to 0.29 & enable c-api tests Jun 12, 2026
@bschoenmaeckers bschoenmaeckers marked this pull request as ready for review June 12, 2026 08:10
implementation=CPython
version=3.14
implementation=RustPython
version=3.15

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abi3t is only supported from 3.15 onwards. This does not require actual 3.15 runtime support.

@bschoenmaeckers

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

@bschoenmaeckers

Copy link
Copy Markdown
Contributor Author

@youknowone This is ready for review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/vm/src/builtins/int.rs (1)

318-334: 💤 Low value

Consider fixing as_u32_mask to match the correct pattern in as_u64_mask.

The loop out = out.wrapping_shl(32) | digit has a subtle issue: for u32, wrapping_shl(32) shifts by 32 % 32 = 0 bits (per Rust semantics), so it effectively ORs all digits together rather than extracting only the lower 32 bits.

The new as_u64_mask correctly takes only the first N digits. Consider applying the same approach here:

Suggested fix
     pub fn as_u32_mask(&self) -> u32 {
         let v = self.as_bigint();
         v.to_u32()
             .or_else(|| v.to_i32().map(|i| i as u32))
             .unwrap_or_else(|| {
-                let mut out = 0u32;
-                for digit in v.iter_u32_digits() {
-                    out = out.wrapping_shl(32) | digit;
-                }
+                let out = v.iter_u32_digits().next().unwrap_or(0);
                 match v.sign() {
-                    Sign::Minus => out * -1i32 as u32,
+                    Sign::Minus => out.wrapping_neg(),
                     _ => out,
                 }
             })
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/builtins/int.rs` around lines 318 - 334, The loop in the
`as_u32_mask` method has a bug where `wrapping_shl(32)` on a u32 shifts by 0
bits (since 32 % 32 = 0 in Rust), so it ORs all digits together rather than
extracting only the lower 32 bits. Fix this by applying the same pattern used in
`as_u64_mask`, which correctly takes only the first N digits needed. Replace the
loop logic to extract just the lower 32 bits from the bigint digits instead of
accumulating all digits with an ineffective shift operation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/vm/src/builtins/int.rs`:
- Around line 318-334: The loop in the `as_u32_mask` method has a bug where
`wrapping_shl(32)` on a u32 shifts by 0 bits (since 32 % 32 = 0 in Rust), so it
ORs all digits together rather than extracting only the lower 32 bits. Fix this
by applying the same pattern used in `as_u64_mask`, which correctly takes only
the first N digits needed. Replace the loop logic to extract just the lower 32
bits from the bigint digits instead of accumulating all digits with an
ineffective shift operation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 7decf53d-4d5a-46fc-b2b9-f48922df202c

📥 Commits

Reviewing files that changed from the base of the PR and between 901b388 and e8ad970.

📒 Files selected for processing (2)
  • crates/capi/src/longobject.rs
  • crates/vm/src/builtins/int.rs

@ShaharNaveh ShaharNaveh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The capi came out really clean. I like it a lot!

Thanks for all the work you've put into it<3

@fanninpm

Copy link
Copy Markdown
Contributor

@bschoenmaeckers is this ready to go?

@bschoenmaeckers

Copy link
Copy Markdown
Contributor Author

@bschoenmaeckers is this ready to go?

Yes this is ready to merge!

@fanninpm

Copy link
Copy Markdown
Contributor

@youknowone can you take a look at this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants