use super::*;
use crate::core::encoder::{encoder_from_owned, Encoder};
use proptest::prelude::*;
use rustc_hash::FxHashMap;
use super::encode::{byte_pair_encode_ids_seeded_into, Seeding};
use super::ranks::{BytePairRanks, RankLookup};
fn ids_seeded(
piece: &[u8],
merge_ranks: &Encoder,
id_encoder: &Encoder,
char_granular: bool,
) -> Vec<u32> {
let mut out = Vec::new();
byte_pair_encode_ids_seeded_into(
piece,
RankLookup::new(merge_ranks),
id_encoder,
match char_granular {
true => Seeding::Chars,
false => Seeding::Bytes,
},
&mut out,
);
out
}
#[derive(Debug, Clone, Copy)]
struct RefNode {
prev: usize,
next: usize,
rank: u32,
start: usize,
len: usize,
}
fn byte_pair_encode_reference(
piece: &[u8],
merge_ranks: &Encoder,
id_encoder: &Encoder,
) -> Vec<u32> {
byte_pair_encode_reference_seeded(piece, merge_ranks, id_encoder, false)
}
fn byte_pair_encode_reference_seeded(
piece: &[u8],
merge_ranks: &Encoder,
id_encoder: &Encoder,
char_granular: bool,
) -> Vec<u32> {
if piece.is_empty() {
return vec![];
}
if piece.len() == 1 {
return id_encoder.get(piece).map_or(vec![], |r| vec![r]);
}
if let Some(id) = id_encoder.get(piece) {
return vec![id];
}
let spans: Vec<(usize, usize)> = match char_granular
.then(|| std::str::from_utf8(piece).ok())
.flatten()
{
Some(text) => text
.char_indices()
.map(|(start, c)| (start, c.len_utf8()))
.collect(),
None => (0..piece.len()).map(|start| (start, 1)).collect(),
};
let mut nodes: Vec<RefNode> = Vec::with_capacity(spans.len());
for (i, &(start, len)) in spans.iter().enumerate() {
nodes.push(RefNode {
prev: if i == 0 { usize::MAX } else { i - 1 },
next: if i + 1 == spans.len() {
usize::MAX
} else {
i + 1
},
rank: u32::MAX,
start,
len,
});
}
let get_rank = |left_idx: usize, right_idx: usize, nodes: &[RefNode]| -> u32 {
if left_idx == usize::MAX || right_idx == usize::MAX {
return u32::MAX;
}
let left = &nodes[left_idx];
let right = &nodes[right_idx];
let start = left.start;
let len = left.len + right.len;
let slice = &piece[start..start + len];
merge_ranks.get(slice).unwrap_or(u32::MAX)
};
for i in 0..nodes.len() - 1 {
nodes[i].rank = get_rank(i, nodes[i].next, &nodes);
}
loop {
let mut min_rank = u32::MAX;
let mut min_idx = usize::MAX;
let mut curr = 0;
while nodes[curr].prev != usize::MAX {
curr = nodes[curr].prev;
}
while curr != usize::MAX {
let r = nodes[curr].rank;
if r < min_rank {
min_rank = r;
min_idx = curr;
}
curr = nodes[curr].next;
}
if min_rank == u32::MAX {
break;
}
let next_idx = nodes[min_idx].next;
nodes[min_idx].len += nodes[next_idx].len;
let new_next = nodes[next_idx].next;
nodes[min_idx].next = new_next;
if new_next != usize::MAX {
nodes[new_next].prev = min_idx;
}
if nodes[min_idx].prev != usize::MAX {
let prev = nodes[min_idx].prev;
nodes[prev].rank = get_rank(prev, min_idx, &nodes);
}
nodes[min_idx].rank = get_rank(min_idx, nodes[min_idx].next, &nodes);
}
let mut result = Vec::new();
let mut curr = 0;
while nodes[curr].prev != usize::MAX {
curr = nodes[curr].prev;
}
while curr != usize::MAX {
let node = &nodes[curr];
let slice = &piece[node.start..node.start + node.len];
if let Some(id) = id_encoder.get(slice) {
result.push(id);
} else {
for &byte in slice {
if let Some(id) = id_encoder.get(&[byte][..]) {
result.push(id);
}
}
}
curr = nodes[curr].next;
}
result
}
fn make_encoder() -> Encoder {
let mut encoder = FxHashMap::default();
encoder.insert(b"a".to_vec(), 0);
encoder.insert(b"b".to_vec(), 1);
encoder.insert(b"c".to_vec(), 2);
encoder.insert(b"ab".to_vec(), 3);
encoder.insert(b"bc".to_vec(), 4);
encoder.insert(b"abc".to_vec(), 5);
encoder_from_owned(encoder)
}
#[test]
fn test_single_byte() {
let encoder = make_encoder();
assert_eq!(byte_pair_encode(b"a", &encoder), vec![0]);
}
#[test]
fn test_simple_merge() {
let encoder = make_encoder();
assert_eq!(byte_pair_encode(b"ab", &encoder), vec![3]);
}
#[test]
fn test_chain_merge() {
let encoder = make_encoder();
assert_eq!(byte_pair_encode(b"abc", &encoder), vec![5]);
}
#[test]
fn test_empty() {
let encoder = make_encoder();
let empty: Vec<u32> = vec![];
assert_eq!(byte_pair_encode(b"", &encoder), empty);
}
#[test]
fn test_no_merge_possible() {
let encoder = make_encoder();
assert_eq!(byte_pair_encode(b"ac", &encoder), vec![0, 2]);
}
fn tie_encoder() -> Encoder {
let mut encoder = FxHashMap::default();
encoder.insert(b"a".to_vec(), 0);
encoder.insert(b"aa".to_vec(), 1);
encoder_from_owned(encoder)
}
#[test]
fn test_tiebreak_leftmost_wins() {
let encoder = tie_encoder();
assert_eq!(byte_pair_encode(b"aaa", &encoder), vec![1, 0]);
assert_eq!(byte_pair_encode(b"aaaaa", &encoder), vec![1, 1, 0]);
assert_eq!(
byte_pair_encode_reference(b"aaa", &encoder, &encoder),
vec![1, 0]
);
assert_eq!(
byte_pair_encode_reference(b"aaaaa", &encoder, &encoder),
vec![1, 1, 0]
);
}
fn tokens_only(pieces: Vec<Piece>) -> Vec<u32> {
pieces
.into_iter()
.filter_map(|p| match p {
Piece::Token(id) => Some(id),
Piece::Unresolved { .. } => None,
})
.collect()
}
#[test]
fn test_tiebreak_leftmost_wins_multibyte_chars() {
let mut encoder = FxHashMap::default();
encoder.insert("▁".as_bytes().to_vec(), 0);
encoder.insert("▁▁".as_bytes().to_vec(), 1);
let encoder = encoder_from_owned(encoder);
let piece = "▁▁▁".as_bytes();
assert_eq!(
tokens_only(byte_pair_encode_pieces_seeded(
piece,
RankLookup::new(&encoder),
&encoder,
true
)),
vec![1, 0]
);
assert_eq!(
byte_pair_encode_reference_seeded(piece, &encoder, &encoder, true),
vec![1, 0]
);
}
fn prop_encoder() -> Encoder {
let mut encoder = FxHashMap::default();
let tokens: [&[u8]; 18] = [
b"a", b"b", b"c", b"d", b"aa", b"ab", b"ba", b"bb", b"cd", b"dc", b"cc", b"aaa", b"aab",
b"abab", b"aaaa", b"bcd", b"abcd", b"abc",
];
for (i, token) in tokens.iter().enumerate() {
encoder.insert(token.to_vec(), i as u32);
}
encoder_from_owned(encoder)
}
fn prop_two_maps() -> (Encoder, Encoder) {
let ranked: [(&[u8], u32); 13] = [
(b"aa", 1),
(b"ab", 1),
(b"bb", 1),
(b"ba", 2),
(b"cc", 2),
(b"cd", 3),
(b"dc", 3),
(b"aaa", 4),
(b"aab", 4),
(b"abb", 4),
(b"abab", 5),
(b"aaaa", 5),
(b"abcd", 6),
];
let mut merge_ranks = FxHashMap::default();
for (token, rank) in ranked {
merge_ranks.insert(token.to_vec(), rank);
}
let ids: [&[u8]; 17] = [
b"abcd", b"aaaa", b"abab", b"abb", b"aab", b"aaa", b"dc", b"cd", b"cc", b"ba", b"bb",
b"ab", b"aa", b"d", b"c", b"b", b"a",
];
let mut id_encoder = FxHashMap::default();
for (i, token) in ids.iter().enumerate() {
id_encoder.insert(token.to_vec(), i as u32);
}
(
encoder_from_owned(merge_ranks),
encoder_from_owned(id_encoder),
)
}
fn char_prop_maps() -> (Encoder, Encoder) {
let ranked: [(&str, u32); 10] = [
("aa", 1),
("ab", 1),
("▁a", 1),
("b▁", 2),
("中😀", 2),
("😀😀", 2),
("aab", 3),
("ab▁", 3),
("中😀中", 4),
("aaaa", 5),
];
let mut merge_ranks = FxHashMap::default();
for (token, rank) in ranked {
merge_ranks.insert(token.as_bytes().to_vec(), rank);
}
let ids: [&str; 14] = [
"a",
"b",
"▁",
"中",
"😀",
"aaaa",
"中😀中",
"ab▁",
"aab",
"😀😀",
"b▁",
"▁a",
"ab",
"aa",
];
let mut id_encoder = FxHashMap::default();
for (i, token) in ids.iter().enumerate() {
id_encoder.insert(token.as_bytes().to_vec(), i as u32);
}
(
encoder_from_owned(merge_ranks),
encoder_from_owned(id_encoder),
)
}
#[test]
fn test_long_single_char_run() {
let encoder = tie_encoder();
let piece = vec![b'a'; 4096];
let expected = vec![1u32; 2048];
assert_eq!(byte_pair_encode(&piece, &encoder), expected);
assert_eq!(
byte_pair_encode_reference(&piece, &encoder, &encoder),
expected
);
}
#[test]
fn test_repeated_ab() {
let encoder = prop_encoder();
let piece = b"ab".repeat(512);
assert_eq!(
byte_pair_encode(&piece, &encoder),
byte_pair_encode_reference(&piece, &encoder, &encoder)
);
}
#[test]
fn test_whole_piece_is_one_token() {
let encoder = prop_encoder();
assert_eq!(byte_pair_encode(b"abcd", &encoder), vec![16]);
}
#[test]
fn test_single_byte_and_empty_agree_with_reference() {
let encoder = prop_encoder();
let pieces: [&[u8]; 3] = [b"", b"a", b"z"];
for piece in pieces {
assert_eq!(
byte_pair_encode(piece, &encoder),
byte_pair_encode_reference(piece, &encoder, &encoder),
"piece {piece:?}"
);
}
}
#[test]
fn test_bytes_absent_from_vocab() {
let encoder = prop_encoder();
let pieces: [&[u8]; 4] = [b"azb", b"\xff\xfe", b"abzcd", b"zzz"];
for piece in pieces {
assert_eq!(
byte_pair_encode(piece, &encoder),
byte_pair_encode_reference(piece, &encoder, &encoder),
"piece {piece:?}"
);
}
}
fn ac_only_encoder() -> Encoder {
let mut encoder = FxHashMap::default();
encoder.insert(b"a".to_vec(), 0);
encoder.insert(b"c".to_vec(), 1);
encoder_from_owned(encoder)
}
#[test]
fn test_pieces_reports_unresolved_span() {
let encoder = ac_only_encoder();
assert_eq!(
byte_pair_encode_pieces_seeded(b"abc", RankLookup::new(&encoder), &encoder, false),
vec![
Piece::Token(0),
Piece::Unresolved { start: 1, len: 1 },
Piece::Token(1),
]
);
assert_eq!(byte_pair_encode(b"abc", &encoder), vec![0, 1]);
}
#[test]
fn test_pieces_coalesce_consecutive_unresolved() {
let mut encoder = FxHashMap::default();
encoder.insert(b"a".to_vec(), 0);
encoder.insert(b"d".to_vec(), 1);
let encoder = encoder_from_owned(encoder);
assert_eq!(
byte_pair_encode_pieces_seeded(b"abcd", RankLookup::new(&encoder), &encoder, false),
vec![
Piece::Token(0),
Piece::Unresolved { start: 1, len: 2 },
Piece::Token(1),
]
);
}
#[test]
fn test_pieces_unresolved_at_start_and_end() {
let encoder = ac_only_encoder();
assert_eq!(
byte_pair_encode_pieces_seeded(b"bab", RankLookup::new(&encoder), &encoder, false),
vec![
Piece::Unresolved { start: 0, len: 1 },
Piece::Token(0),
Piece::Unresolved { start: 2, len: 1 },
]
);
}
#[test]
fn test_pieces_full_coverage_has_no_unresolved() {
let encoder = prop_encoder();
for piece in [&b""[..], b"a", b"abcd", b"aabbccdd", b"dcbadcba"] {
let pieces =
byte_pair_encode_pieces_seeded(piece, RankLookup::new(&encoder), &encoder, false);
assert!(
pieces.iter().all(|p| matches!(p, Piece::Token(_))),
"piece {piece:?} produced {pieces:?}"
);
}
}
#[test]
fn test_pieces_single_byte_and_empty_fast_paths() {
let encoder = ac_only_encoder();
let empty: Vec<Piece> = vec![];
assert_eq!(
byte_pair_encode_pieces_seeded(b"", RankLookup::new(&encoder), &encoder, false),
empty
);
assert_eq!(
byte_pair_encode_pieces_seeded(b"a", RankLookup::new(&encoder), &encoder, false),
vec![Piece::Token(0)]
);
assert_eq!(
byte_pair_encode_pieces_seeded(b"b", RankLookup::new(&encoder), &encoder, false),
vec![Piece::Unresolved { start: 0, len: 1 }]
);
}
proptest! {
#[test]
fn prop_matches_reference_single_map(
piece in prop::collection::vec(prop::sample::select(vec![b'a', b'b', b'c', b'd']), 0..200)
) {
let encoder = prop_encoder();
prop_assert_eq!(
byte_pair_encode(&piece, &encoder),
byte_pair_encode_reference(&piece, &encoder, &encoder)
);
}
#[test]
fn prop_matches_reference_arbitrary_bytes(
piece in prop::collection::vec(any::<u8>(), 0..200)
) {
let encoder = prop_encoder();
prop_assert_eq!(
byte_pair_encode(&piece, &encoder),
byte_pair_encode_reference(&piece, &encoder, &encoder)
);
}
#[test]
fn prop_matches_reference_two_maps(
piece in prop::collection::vec(prop::sample::select(vec![b'a', b'b', b'c', b'd', b'z']), 0..200)
) {
let (merge_ranks, id_encoder) = prop_two_maps();
prop_assert_eq!(
byte_pair_encode_with_ranks(&piece, &merge_ranks, &id_encoder),
byte_pair_encode_reference(&piece, &merge_ranks, &id_encoder)
);
}
#[test]
fn prop_pieces_tokens_match_with_ranks(
piece in prop::collection::vec(any::<u8>(), 0..200)
) {
let encoder = prop_encoder();
let tokens_only: Vec<u32> = byte_pair_encode_pieces_seeded(&piece, RankLookup::new(&encoder), &encoder, false)
.into_iter()
.filter_map(|p| match p {
Piece::Token(id) => Some(id),
Piece::Unresolved { .. } => None,
})
.collect();
prop_assert_eq!(
tokens_only,
byte_pair_encode_with_ranks(&piece, &encoder, &encoder)
);
}
#[test]
fn prop_char_seeded_matches_reference(
chars in prop::collection::vec(
prop::sample::select(vec!['a', 'b', '▁', '中', '😀']), 0..200)
) {
let (merge_ranks, id_encoder) = char_prop_maps();
let text: String = chars.into_iter().collect();
let piece = text.as_bytes();
prop_assert_eq!(
tokens_only(byte_pair_encode_pieces_seeded(piece, RankLookup::new(&merge_ranks), &id_encoder, true)),
byte_pair_encode_reference_seeded(piece, &merge_ranks, &id_encoder, true)
);
}
#[test]
fn prop_byte_pair_table_agrees_with_the_map(
entries in prop::collection::vec(
(prop::collection::vec(any::<u8>(), 1..5), any::<u32>()), 0..40),
probes in prop::collection::vec(prop::collection::vec(any::<u8>(), 0..5), 1..40)
) {
let map: Encoder = entries.into_iter().collect();
let pairs = BytePairRanks::build(&map);
let plain = RankLookup::new(&map);
let fronted = RankLookup::with_pairs(&map, &pairs);
for probe in &probes {
prop_assert_eq!(fronted.get(probe), plain.get(probe), "diverged on {:?}", probe);
}
}
#[test]
fn prop_ids_seeded_matches_pieces_seeded(
piece in prop::collection::vec(any::<u8>(), 0..200),
char_granular in any::<bool>()
) {
let (merge_ranks, id_encoder) = prop_two_maps();
prop_assert_eq!(
ids_seeded(&piece, &merge_ranks, &id_encoder, char_granular),
tokens_only(byte_pair_encode_pieces_seeded(&piece, RankLookup::new(&merge_ranks), &id_encoder, char_granular
))
);
}
#[test]
fn prop_selection_strategies_agree_across_the_threshold(
piece in prop::collection::vec(
prop::sample::select(vec![b'a', b'b', b'c', b'd']), 130..260)
) {
let encoder = prop_encoder();
for len in [32usize, 63, 64, 65, 129, piece.len()] {
let slice = &piece[..len.min(piece.len())];
prop_assert_eq!(
byte_pair_encode(slice, &encoder),
byte_pair_encode_reference(slice, &encoder, &encoder),
"diverged at {} symbols", slice.len()
);
}
}
}
mod char_seeding {
use super::*;
use crate::core::bpe::ranks::PairRanks;
use crate::core::byte_level::byte_level_encode;
const CORPUS: &[&str] = &[
"中国人民",
"中文字典",
"人民日报",
"日本語",
"日本の中国",
"the quick brown",
"the中国",
" 中国 the",
"한국어",
"한국 사람",
"中国한국",
"quick中",
"языки",
"я中国",
];
fn train(merges: usize) -> (Encoder, Encoder) {
let mut words: Vec<Vec<Vec<u8>>> = CORPUS
.iter()
.map(|w| w.as_bytes().iter().map(|&b| vec![b]).collect())
.collect();
let mut raw: Vec<Vec<u8>> = (0..=u8::MAX).map(|b| vec![b]).collect();
for _ in 0..merges {
let mut counts: FxHashMap<(Vec<u8>, Vec<u8>), usize> = FxHashMap::default();
for word in &words {
for pair in word.windows(2) {
*counts
.entry((pair[0].clone(), pair[1].clone()))
.or_default() += 1;
}
}
let Some((best, _)) = counts
.into_iter()
.max_by(|a, b| a.1.cmp(&b.1).then_with(|| b.0.cmp(&a.0)))
else {
break;
};
let mut joined = best.0.clone();
joined.extend_from_slice(&best.1);
if raw.contains(&joined) {
break;
}
raw.push(joined.clone());
for word in &mut words {
let mut at = 0;
while at + 1 < word.len() {
if word[at] == best.0 && word[at + 1] == best.1 {
word[at] = joined.clone();
word.remove(at + 1);
} else {
at += 1;
}
}
}
}
let raw_encoder: Encoder = raw
.iter()
.enumerate()
.map(|(id, bytes)| (bytes.as_slice(), id as u32))
.collect();
let mapped: Vec<(String, u32)> = raw
.iter()
.enumerate()
.map(|(id, bytes)| (byte_level_encode(bytes), id as u32))
.collect();
let id_encoder: Encoder = mapped
.iter()
.map(|(text, id)| (text.as_bytes(), *id))
.collect();
(id_encoder, raw_encoder)
}
#[test]
fn the_trained_vocabulary_contains_straddling_tokens() {
let (_, raw_encoder) = train(400);
let straddling = raw_encoder
.keys()
.filter(|key| {
std::str::from_utf8(key).is_err()
&& key[1..].iter().any(|&b| !(0x80..0xC0).contains(&b))
})
.count();
assert!(
straddling > 0,
"the training corpus must produce tokens crossing character boundaries"
);
}
#[test]
fn seeding_characters_whole_matches_seeding_their_bytes() {
let (id_encoder, raw_encoder) = train(400);
let rank_map = id_encoder.clone();
let table = PairRanks::build(&rank_map, &id_encoder, Some(&raw_encoder))
.expect("the trained vocabulary is addressable by id");
assert!(
table.seeds_chars(),
"some character of this vocabulary must be safe to seed whole"
);
let multi_byte: Vec<&[u8]> = raw_encoder
.keys()
.filter(|key| {
key.len() > 1
&& std::str::from_utf8(key).is_ok_and(|text| text.chars().nth(1).is_none())
})
.collect();
let vouched = multi_byte
.iter()
.filter(|key| table.char_seed(key) != u32::MAX)
.count();
assert!(vouched > 0, "no character was vouched for");
assert!(
vouched < multi_byte.len(),
"every character was vouched for, so the safety test refused nothing"
);
let ranks = RankLookup::new(&rank_map).with_ids(Some(&table));
let alphabet: Vec<&str> = vec![
"中", "国", "人", "民", "日", "本", "語", "한", "국", "어", "я", "з", "ы", "the",
"quick", " ", "中国", "日本", "한국",
];
let mut runner = proptest::test_runner::TestRunner::deterministic();
let strategy = proptest::collection::vec(0usize..alphabet.len(), 0..20);
runner
.run(&strategy, |picks| {
let text: String = picks.iter().map(|&i| alphabet[i]).collect();
let (mut chars, mut bytes) = (Vec::new(), Vec::new());
byte_pair_encode_ids_seeded_into(
text.as_bytes(),
ranks,
&raw_encoder,
Seeding::RawChars,
&mut chars,
);
byte_pair_encode_ids_seeded_into(
text.as_bytes(),
ranks,
&raw_encoder,
Seeding::RawBytes,
&mut bytes,
);
prop_assert_eq!(chars, bytes, "diverged on {:?}", text);
Ok(())
})
.expect("character seeding must equal byte seeding on every generated string");
}
}
mod ids_or_pieces {
use super::*;
use crate::core::bpe::byte_pair_encode_ids_or_pieces;
fn both(piece: &[u8], seeding: Seeding) -> (Option<Vec<Piece>>, Vec<u32>, Vec<Piece>) {
let (merge_ranks, id_encoder) = prop_two_maps();
let table = PairRanks::build(&merge_ranks, &id_encoder, None)
.expect("this vocabulary is addressable by id");
let ranks = RankLookup::new(&merge_ranks).with_ids(Some(&table));
let mut ids = Vec::new();
let pieces = byte_pair_encode_ids_or_pieces(piece, ranks, &id_encoder, seeding, &mut ids);
let reference = byte_pair_encode_pieces_seeded(
piece,
RankLookup::new(&merge_ranks),
&id_encoder,
seeding == Seeding::Chars,
);
(pieces, ids, reference)
}
#[test]
fn a_representable_piece_is_answered_in_ids() {
for piece in [&b"abab"[..], b"aaaa", b"abcd", b"a", b"", b"bbba"] {
let (pieces, ids, reference) = both(piece, Seeding::Bytes);
assert!(
pieces.is_none(),
"{piece:?} is representable and must not reach the fallback path"
);
assert_eq!(ids, tokens_only(reference), "diverged on {piece:?}");
}
}
#[test]
fn an_unrepresentable_piece_is_answered_in_pieces() {
for piece in [&b"z"[..], b"az", b"zab", b"abzab", b"zz"] {
let (pieces, ids, reference) = both(piece, Seeding::Bytes);
assert!(
ids.is_empty(),
"{piece:?} needs the fallback, so nothing may be written to the id buffer"
);
assert_eq!(pieces, Some(reference), "diverged on {piece:?}");
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(400))]
#[test]
fn prop_ids_or_pieces_matches_pieces_seeded(
piece in prop::collection::vec(any::<u8>(), 0..200),
char_granular in any::<bool>()
) {
let (merge_ranks, id_encoder) = prop_two_maps();
let table = PairRanks::build(&merge_ranks, &id_encoder, None)
.expect("this vocabulary is addressable by id");
let ranks = RankLookup::new(&merge_ranks).with_ids(Some(&table));
let seeding = match char_granular {
true => Seeding::Chars,
false => Seeding::Bytes,
};
let mut ids = Vec::new();
let pieces = byte_pair_encode_ids_or_pieces(&piece, ranks, &id_encoder, seeding, &mut ids);
let reference = byte_pair_encode_pieces_seeded(
&piece, RankLookup::new(&merge_ranks), &id_encoder, char_granular);
match pieces {
None => {
prop_assert!(
!reference.iter().any(|p| matches!(p, Piece::Unresolved { .. })),
"answered in ids, but the piece merge found something unresolved"
);
prop_assert_eq!(ids, tokens_only(reference));
}
Some(pieces) => {
prop_assert!(ids.is_empty(), "the fallback path must write no ids");
prop_assert_eq!(pieces, reference);
}
}
}
}