Wrap raw dicts passed to bind= in Binding classes so validation errors use a live object's schema
Follow-up from the review of #4090 (fix for #3752).
Problem
When a user passes a raw dict to bind=, it stays untyped until to_dict() validation:
import altair as alt
alt.param(
name="threshold",
value=5,
bind={"input": "ragne", "min": 0, "max": 10}, # typo: "ragne"
)There is no live Binding object at the error path, so error reporting has to guess the intended branch of the Binding union from the jsonschema error tree. In #4090 that required keeping a conservative branch-selection fallback (~340 lines of heuristics) whose remaining use case is raw-dict union inputs like this one.
Context
The other half of #3752 was solved by wrapping datum= in its *Datum channel class so it behaves like value= (wrapped in *Value), giving error reporting a live object with a known schema to re-validate against. bind= is the remaining case where a user-supplied dict reaches validation untyped.
Suggested direction
Wrap raw dicts passed to bind= at construction, mirroring the datum=/value= wrapping. The complication: bind= is a union whose branches are discriminated by const/enum on input, and an invalid discriminator (a typo) cannot be matched to a branch cleanly — so the mechanism needs to handle "no branch matches" without degrading today's error ('ragne' is an invalid value for 'input', which main already produces).
Success criterion: the bind= typo case keeps its current error, and the branch-selection fallback added in #4090 can be removed.
Source: vega/altair