Skip to content

feat(binding): execute a command with each value a stream produces - #92

Merged
glennawatson merged 3 commits into
mainfrom
feat/invoke-command
Sep 10, 2026
Merged

feat(binding): execute a command with each value a stream produces#92
glennawatson merged 3 commits into
mainfrom
feat/invoke-command

Conversation

@glennawatson

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Feature.

What is the new behavior?

InvokeCommand executes a command with each value an observable produces, resolved at compile time.

  • The overload naming a command property is generated. The path to the command is observed with the mechanism the generator picked for the declaring type, and every value the stream produces is offered to whichever command that path currently holds. A chain is followed, so replacing a parent retargets the execution.
  • The overload taking the command itself is served by the runtime library. There is no property to observe and nothing to resolve, so no dispatch is emitted for it.
  • CanExecute is asked per emission rather than tracked from CanExecuteChanged: the value being offered is the command parameter, so only the command can answer for that value.
    • A value the command refuses is dropped.
    • A null command - a property not yet assigned, or a path through an absent parent - drops the values offered while it stands.
    • Replacing the command executes nothing by itself.
  • A call site the generator cannot claim reads the property through the runtime expression engine, which is what the stub's trimming annotation says.
  • The diagnostics follow. RXUIBIND002 names the type holding the command rather than the value type of the stream, and RXUIBIND011 reports an InvokeCommand that ReactiveUI's own mixin answered.

What is the current behavior?

The package has no InvokeCommand. A consumer reaches ReactiveUI's mixin for it, which resolves the command property through the runtime expression engine - reflection on a path the compiler already knows.

What might this PR break?

None.

  • The two overloads naming ReactiveCommandBase are not offered, because naming that type would put a ReactiveUI reference in this package. A ReactiveCommand is reached through the ICommand overloads, with the parameter arriving as object rather than the command's declared input type.

Checklist

  • I have read the Contribute guide
  • Tests have been added or updated (for bug fixes / features)
  • Docs have been added or updated (for bug fixes / features)
  • Changes target the main branch
  • PR title follows Conventional Commits

Additional information

The gating and the executions live in one runtime type that both the generated call site and the expression engine hand their command to, so the two paths cannot drift. The AOT validation harness gains an InvokeCommand scenario, and the shared-scenario sweep carries the new scenarios onto the System.Reactive package.

- InvokeCommand joins the generated APIs. The overload naming a command property
  observes that path with the mechanism the generator picked for the declaring
  type, and offers every value the stream produces to whichever command the path
  currently holds - a chain is followed, so replacing a parent retargets the
  execution.
- The overload taking the command itself has nothing to resolve and nothing to
  observe, so the runtime library serves it outright and no dispatch is emitted.
- CanExecute is asked per emission rather than tracked from CanExecuteChanged:
  the value being offered is the command parameter, so only the command can
  answer for that value. A refused value is dropped, a null command drops the
  values offered while it stands, and replacing the command executes nothing by
  itself.
- Both paths hand the resolved command to CommandInvoker, so a generated call
  site emits one call and the runtime engine's path cannot drift from it.
- A call site the generator cannot claim reads the property through the runtime
  expression engine, which is what the stub's trimming annotation says.
- RXUIBIND002 reads the type argument naming the observed object, which for this
  API is the second: the first is the value type of a stream the caller already
  built. RXUIBIND011 covers the API as well, so a call answered by ReactiveUI's
  own mixin is reported.
- The overloads naming ReactiveCommandBase are not offered: naming that type
  would put a ReactiveUI reference in this package. A ReactiveCommand is reached
  through the ICommand overloads, with the parameter arriving as object.
@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.49%. Comparing base (74bd257) to head (e7b6570).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #92      +/-   ##
==========================================
+ Coverage   99.48%   99.49%   +0.01%     
==========================================
  Files         251      258       +7     
  Lines       10652    10888     +236     
  Branches     1181     1209      +28     
==========================================
+ Hits        10597    10833     +236     
  Misses         42       42              
  Partials       13       13              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

… it surfaces

- A call the model could not resolve, a target held in a type parameter, and a
  selector whose body is no property path each reach a guard that had no test.
- A fault in the values is surfaced while a command is being observed, which is
  a different observer from the one a fixed command uses.
@sonarqubecloud

Copy link
Copy Markdown

@glennawatson
glennawatson merged commit 032fe47 into main Sep 10, 2026
12 checks passed
@glennawatson
glennawatson deleted the feat/invoke-command branch September 10, 2026 02:40
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.

1 participant