Skip to content

feat(parity): add the api parity file and its test - #411

Open
YuanYuYuan wants to merge 23 commits into
mainfrom
docs/parity-follow-up
Open

YuanYuYuan wants to merge 23 commits into
mainfrom
docs/parity-follow-up

Conversation

@YuanYuYuan

@YuanYuYuan YuanYuYuan commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR adds api-parity.toml, a table of builder options per entity and per binding (Rust, Python, C and Go). It also adds a CI test that fails when a public with_* builder method has no row. The scan reads only impl headers that start a line and the macro impl_with_type_info!. It demands rows only for Rust with_* methods, so an option that exists only in one binding needs no row. The table already holds the rows for the options that the other 0.2.2 pull requests add.

Merge this PR after those pull requests, because the test fails on a builder method that has no row. This PR starts from the v0.2.1 tag and carries the rows, so it passes only on a tree that holds the other changes.

Key Changes

Area Change
api-parity.toml One row per builder option, for each entity. Each cell is present with a symbol, absent with a reason, or planned with a reason and a release.
crates/hiroz-tests/tests/api_parity.rs Parser test, builder scan, symbol check and control tests. A present Python, C or Go symbol must be defined in the source of its binding, outside comments and strings. For Python and C, a definition is a Rust fn, struct, enum, union, trait, type, const or static item. A parameter of a fn, a field of a struct, a PyO3 signature parameter and a PyO3 rename attribute also count. For Go, it is a func or type line. The check reads nested Rust block comments and raw strings. A call, a struct literal, a let binding, a closure parameter and a format argument do not count. It runs in the pure-Rust CI job. The scan reads pub fn, pub async fn, pub const fn and pub unsafe fn.
crates/hiroz-tests/Cargo.toml The toml crate as a dependency of the test crate.
CONTRIBUTING.md A short note that a new builder method needs a row, and what the scan and the symbol check cover.
api-parity.toml header States what the symbol check proves. Each identifier of a present Python, C or Go symbol is defined in the source of that binding, outside comments and strings. The header no longer says that the symbol exists as a whole.

Rows for the options of the other 0.2.2 pull requests: the node subscriber options, the publisher locality, the cache locality and the cache expected encoding. The cells for the C and Go buffer group, the C quality-of-service v2 structs and the C action client status topic are present. The Go context namespace is planned for 0.3.0.

The file has no entity for parameters or lifecycle nodes. It has no rows for events. It does not compare behaviour between bindings. The symbol check does not tie a method to its type or a parameter to its function: a parameter or field name counts when any function or struct of the binding has it. It does not check enum variants, impl blocks, macro-generated items, Go struct fields or Go parameters, and it does not run the binding.

Before and after

Before After
Parity file none api-parity.toml
New with_* method on a builder needs no row fails the test until it has a row
Row that names a missing symbol not possible fails the test
Scan that finds no builder method not possible fails the test
present cell for Python, C or Go not possible fails the test when an identifier of the symbol has no definition in the source of that binding. A mention in a comment, a nested comment, a string or a raw string, a commented-out rename attribute, a call, a struct literal or a let binding does not count
Builder method declared pub async fn or pub const fn not scanned scanned
Test Before After
parity_file_has_valid_shape fails: the file does not exist passes
every_with_method_has_a_row does not exist passes on a tree that holds the other 0.2.2 changes
a_new_with_method_fails_until_it_has_a_row does not exist passes: a new method without a row fails
deleting_a_row_fails_the_check does not exist passes: it removes one row and expects a failure
scan_reads_impl_headers_and_macro_calls does not exist passes. With the async and const scan change reverted, it fails
a_symbol_that_is_only_mentioned_fails does not exist passes: a symbol that occurs only in a comment, a string or a call is reported for each of the three bindings, and the same symbol passes once it is defined
definition_in_a_commented_rename_fails does not exist: the file is absent on main passes: a rename in a comment or a string is not a definition, and a live rename is
definition_in_a_nested_rust_comment_fails does not exist passes
definition_after_a_rust_raw_string_is_read_correctly does not exist passes
a_name_that_is_not_a_parameter_or_field_fails does not exist passes
multibyte_text_before_a_rename_keeps_offsets_stable does not exist passes

What fails without this

Nothing fails on the tag today. A builder option can ship in Rust with no Python, C or Go option, and no file records the gap. A later pull request that adds a with_* method passes CI with no row. With this PR, it fails with a message that names the method.

The first commit is red because the file does not exist. On a tree without the rows of the other pull requests, every_with_method_has_a_row, deleting_a_row_fails_the_check and a_new_with_method_fails_until_it_has_a_row fail and name the four new methods. That failure is the reason this PR lands last.

Evidence

The parity tests ran on a scratch merge. It joins the head of this branch and the heads of the other sixteen 0.2.2 changes: fix/embedded-msgs-crates-io, chore/yanked-crates, chore/uv-lock-check, fix/go-sequence-length-bound, fix/go-time-array, fix/go-nested-fixed-array, fix/python-send-response-id, docs/python-binding-guide, feat/node-group-options, feat/c-go-group-choice, feat/cache-check-parity, feat/c-abi-version-guard, feat/raw-subscriber-buffer-fields, feat/c-qos-v2, fix/go-typed-subscriber-nil-receiver and feat/go-context-variants. The merge commit is not published. Two conflicts needed a hand edit. In crates/hiroz/src/ffi/service.rs, the merge takes the _with_qos call from the QoS change and the ffi_warn! macro from the version change. In crates/hiroz-go/hiroz/subscriber_typed.go, it keeps both import lists. The merge does not hold the CI wiring change or the release change.

Claim Basis
The parity tests pass on a tree with the other 0.2.2 changes cargo nextest run --no-tests=fail -p hiroz-tests --features ros-msgs,jazzy,raw-sub-tests -E 'binary(api_parity)' runs 47 tests and all 47 pass (rc 0) on that merge, with this branch at 58b539019
The stricter symbol check accepts every real present cell present_symbols_exist_in_each_binding is one of those tests. It passes with every present cell of api-parity.toml unchanged
The stricter symbol check rejects the gaps The tests in the table above, one per gap, are among those tests. The red results in the table above were measured at earlier heads and not re-run at 58b539019. The tests ffe66d6ec adds and the multibyte test a9409ff7e adds were each red before their fix
Lint cargo clippy -p hiroz-tests --no-deps --all-targets --features ros-interop,hu-meter-tests,hu-monitor-tests,raw-sub-tests,jazzy -- -D warnings ends with rc 0 on the same merge
The builder scan ignores comments and strings, and a named = argument in a call inside a PyO3 default is not a definition builder_scan_ignores_comments_and_strings, builder_scan_keeps_methods_after_a_raw_string_with_a_closing_brace and a_format_argument_in_a_python_default_is_not_a_definition pass in the same run. Not run against the old scan: no red result was measured for them
The scanner gaps are closed a_format_argument_in_a_brace_delimited_macro_is_not_a_definition, builder_scan_reads_an_impl_header_that_spans_lines and a_symbol_without_an_identifier_fails, a_string_default_of_a_parameter_called_name_is_not_a_rename and a_pyo3_signature_parameter_defines_a_python_name_only and a_forbidden_field_is_rejected_whatever_its_value pass on 58b539019. Each fails when its own fix is reverted alone: brace depth, the joined impl header, the empty-identifier check, the depth of a rename, the Python-only signature rule, the key check of a forbidden cell field
The scan fix is covered not re-measured at 58b539019. An earlier run with the async and const scan change reverted failed only scan_reads_impl_headers_and_macro_calls
They fail without the rows of the other pull requests not re-measured at 58b539019. An earlier run on a tree that lacked the rows failed three tests, every_with_method_has_a_row, deleting_a_row_fails_the_check and a_new_with_method_fails_until_it_has_a_row

Breaking Changes

None.

The parity file does not exist yet, so the test that reads it fails. The other tests prove the shape check rejects each defect.
The file lists each builder option per entity and per binding, with a state for every cell. It makes the gaps between Rust, Python, C and Go visible.
The test scans the builder types in crates/hiroz/src and fails when a public with_* method has no parity row, or when a row names a symbol that does not exist. Control tests prove that an empty scan, a deleted row and a new method each fail.
A contributor who adds a builder method must add a row. The note says so before the test fails.
Clippy with -D warnings flags both.
The header said every symbol exists in the source of its binding. The check requires each identifier to be defined there, outside comments and strings. State that, in the same words as the test and CONTRIBUTING.md.
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://ZettaScaleLabs.github.io/hiroz/pr-preview/pr-411/

Built to branch gh-pages at 2026-10-30 20:35 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Scanner defects can accept nonexistent symbols or miss real builder methods, weakening the new parity checks.

2 open issues
What changed in this PR

Adds a cross-binding API parity inventory and automated checks to track builder options across Rust, Python, C and Go.

Changes:

  • Records available, absent and planned options, including those from prerequisite 0.2.2 changes.
  • Adds table validation, builder-method scanning and binding-symbol checks with regression controls.
  • Documents parity maintenance requirements and typed action-client feedback defaults.
File Description
docs/​user-guide/​interop.md Clarifies that typed action-client feedback follows node subscriber defaults.
crates/​hiroz-tests/​tests/​api_parity.rs Adds parity validation, source scanners and control tests.
crates/​hiroz-tests/​Cargo.toml Adds the TOML parsing dependency.
CONTRIBUTING.md Documents parity-row requirements and scanner limitations.
Cargo.lock Records the test crate’s TOML dependency.
api-parity.toml Introduces the per-entity, cross-binding option inventory.

Review effort: Balanced

Comment thread crates/hiroz-tests/tests/api_parity.rs Resolved
Comment thread crates/hiroz-tests/tests/api_parity.rs Resolved
The change that adds the node default now carries the corrected sentence. Keeping the edit here conflicts with the neighbouring bullets of that change.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The parity checks can miss API mismatches because they scan commented-out methods, skip multiline impl headers, and accept malformed symbols or status fields.

2 open issues
3 previously missed issues

In code that hasn't changed since last review

Medium severity Validate supplied fields before status-specific checks

crates/​hiroz-tests/​tests/​api_parity.rs:96

non_empty_str treats an invalid field as though it were absent, which lets malformed cells pass the shape check. For example, a valid present cell with reason = 123 or release = "" is accepted even though those keys are forbidden for that status; the same bypass applies to the forbidden fields of absent and planned cells. Validate any supplied field as a nonempty string before applying the status-specific checks.

Medium severity Parse complete multiline impl headers

crates/​hiroz-tests/​tests/​api_parity.rs:469

A column-zero impl with a multiline generic header is silently skipped because impl_self_type receives only its first line and cannot find the closing >. When other blocks supply the builder's existing methods, the positive controls still pass, so new with_* methods in the skipped block need no parity rows. Accumulate the complete header before extracting its self type, and add a multiline-header regression case.

Medium severity Reject symbols that produce no identifiers

crates/​hiroz-tests/​tests/​api_parity.rs:940

A nonempty symbol containing only punctuation, such as "?" or "...", passes shape validation but produces no identifiers here. The loop then performs no existence checks, so a present Python, C or Go cell with no symbol definition still passes CI. Reject symbols that produce an empty identifier list before checking their definitions.

Review effort: Balanced

The builder scan now blanks comments and strings before it reads impl blocks. The pyo3 signature scan reads only the top level of the parameter list, so a named format argument in a default is not a definition.
Braces now count toward the PyO3 signature depth, so a brace-delimited format argument defines nothing. An impl header that spans lines is read up to its opening brace. A present cell whose symbol has no identifier is an error.
A parameter called name in a PyO3 signature is not a rename, so its string default defines nothing. The signature parameter rule applies to the Python binding only, as the module comment says.
A forbidden field was checked as a non-empty string, so symbol = 123, release = [] and reason = "" passed. Check that the key is present.

This branch has not been deployed

No deployments
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.

2 participants