Skip to content

Implement BoundedArbitrary for BTreeMap and BTreeSet#4626

Open
hz2 wants to merge 1 commit into
model-checking:mainfrom
hz2:bounded-arbitrary-btree
Open

Implement BoundedArbitrary for BTreeMap and BTreeSet#4626
hz2 wants to merge 1 commit into
model-checking:mainfrom
hz2:bounded-arbitrary-btree

Conversation

@hz2

@hz2 hz2 commented Jul 9, 2026

Copy link
Copy Markdown

Summary

  • Add BoundedArbitrary implementations for BTreeMap<K, V> and BTreeSet<V>
  • Follow the same pattern as the existing HashMap and HashSet impls in library/kani/src/bounded_arbitrary.rs
  • Add test harnesses in tests/expected/bounded-arbitrary/btree/

Motivation

BTreeMap and BTreeSet operations cause state-space explosion during verification due to tree rebalancing internals (see #1251, #3965). Users currently have no idiomatic way to generate bounded symbolic BTree collections.

The existing BoundedArbitrary impls cover Vec, Box<[T]>, String, HashMap, and HashSet -- but BTreeMap and BTreeSet are absent despite being commonly used in Rust codebases.

This addition lets users write:

let map: BTreeMap<u8, bool> = kani::bounded_any::<_, 4>();

Testing

Added tests/expected/bounded-arbitrary/btree/btree.rs with cover property checks for sizes 0, 1, and 2 on both BTreeMap and BTreeSet, matching the structure of tests/expected/bounded-arbitrary/hash/hash.rs.

Related issues: #1251, #3965

@github-actions github-actions Bot added Z-EndToEndBenchCI Tag a PR to run benchmark CI Z-CompilerBenchCI Tag a PR to run benchmark CI labels Jul 9, 2026
@hz2 hz2 force-pushed the bounded-arbitrary-btree branch from 4a72a28 to 41da7be Compare July 9, 2026 22:49
Add BoundedArbitrary implementations for BTreeMap<K, V> and BTreeSet<V>,
following the same pattern as the existing HashMap and HashSet impls.

BTreeMap and BTreeSet operations cause state-space explosion during
verification due to tree rebalancing internals. Providing a bounded
constructor via BoundedArbitrary gives users an idiomatic way to generate
symbolic BTree collections with controlled size.

Usage:
    let map: BTreeMap<u8, bool> = kani::bounded_any::<_, 4>();
@hz2 hz2 force-pushed the bounded-arbitrary-btree branch from 41da7be to 8e142d8 Compare July 10, 2026 14:06
@hz2 hz2 marked this pull request as ready for review July 10, 2026 14:06
@hz2 hz2 requested a review from a team as a code owner July 10, 2026 14:06
@feliperodri feliperodri requested a review from Copilot July 11, 2026 14:57

Copilot AI 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.

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

{
fn bounded_any<const N: usize>() -> Self {
let mut btree_map = std::collections::BTreeMap::new();
for _ in 0..N {

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.

Non-blocking: Nice parity with the HashMap bounded generator pattern. Could we add a short comment here noting that duplicate K::any() values may overwrite entries, so reachable map sizes are in 0..=N (not necessarily equal to number of successful insert branches)? It may help future readers understand why size coverage still works as expected.

{
fn bounded_any<const N: usize>() -> Self {
let mut btree_set = std::collections::BTreeSet::new();
for _ in 0..N {

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.

Non-blocking: Same thought as map: a brief note that duplicate V::any() values can collapse cardinality would make the bounded-size behavior explicit for BTreeSet.

#[kani::unwind(5)]
fn check_btreemap() {
const BOUND: usize = 2;
let btree_map: std::collections::BTreeMap<u8, bool> = kani::bounded_any::<_, BOUND>();

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.

Non-blocking: Consider adding assert!(btree_map.len() <= BOUND); as an explicit invariant check. The cover! checks already show reachability of 0/1/2, but this makes the upper-bound contract very obvious in the harness.

#[kani::unwind(5)]
fn check_btreeset() {
const BOUND: usize = 2;
let btree_set: std::collections::BTreeSet<u8> = kani::bounded_any::<_, BOUND>();

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.

Non-blocking: Optional consistency nit: add assert!(btree_set.len() <= BOUND); to mirror the bounded invariant explicitly for the set harness too.

@feliperodri

Copy link
Copy Markdown
Contributor

@hz2 don't forget to update the branch and address all CI issues before merging. Thanks for the contribution! 🦀

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

Labels

Z-CompilerBenchCI Tag a PR to run benchmark CI Z-EndToEndBenchCI Tag a PR to run benchmark CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants