Skip to content

fix(types): repeat struct bounds in where-clauses to stop silent widening to Any#372

Merged
ocots merged 2 commits into
mainfrom
fix/where-bound-drops
Jul 12, 2026
Merged

fix(types): repeat struct bounds in where-clauses to stop silent widening to Any#372
ocots merged 2 commits into
mainfrom
fix/where-bound-drops

Conversation

@ocots

@ocots ocots commented Jul 12, 2026

Copy link
Copy Markdown
Member

Context

The 2026-07-12 alias/where bound-dropping audit found 6 genuine bugs: methods whose where {X} clause names a type parameter without repeating the bound the struct already declares, silently widening it to <:Any. This is invisible via isa on concrete instances, but it breaks Julia's method-specificity ranking the moment a competing method exists — either mis-dispatching silently or throwing MethodError: ... is ambiguous.

All changes only tighten where/field bounds. Tightening a bound cannot make a previously-valid call invalid, so there is no behaviour change.

Changes

The 6 fixes

  • Models/model.jl: time_dependence(::Model{TD})where {TD<:TimeDependence} (matches the struct declaration and inner constructor).
  • Components/constraints_accessors.jl: the 5 ConstraintsModel accessors each restrict 4 params to <:Tuple in the type-parameter position but extracted the 5th via a bare where — now repeat <:Tuple on it.

§5 hardening (both verified safe against every construction site, production + tests)

  • CoercedTrajectory: coerce::C is always only/identityC<:Union{typeof(only),typeof(identity)}.
  • MergedTrajectory: comps::C is always Dict{Int,Function}C<:AbstractDict{Int,<:Function} (struct + both hand-written constructors).

Regression guard

  • New test/suite/meta/test_where_bounds.jl (auto-discovered by the runner) asserts each fixed method's induced TypeVar upper bound is the intended bound, not Any — a future re-drop fails loudly here.

Verification

  • Precompile clean (no method-overwrite).
  • Full suite 4014/4014 pass, including the new guard (6/6) and Aqua's ambiguity / unbound-parameter checks (11/11). No masked ambiguity surfaced from the shared ConstraintsModel fixes.

Note

The audit's "exactly one method per name" was slightly off — each accessor also has a convenience Model-forwarding overload. Harmless; the guard uses which(f, Tuple{ConstraintsModel}) to target the correct method.

🤖 Generated with Claude Code

…ning to Any

A `where {X}` clause that names a type parameter without repeating the bound
the struct already declares silently widens it to `<:Any`. This is invisible
via `isa` on concrete instances but breaks Julia's method-specificity ranking
the moment a competing method exists — either mis-dispatching silently or
throwing `MethodError: ... is ambiguous`.

Fixes the 6 sites found in the 2026-07-12 alias/where bound-dropping audit:

- Models/model.jl: `time_dependence(::Model{TD})` now `where {TD<:TimeDependence}`.
- Components/constraints_accessors.jl: the 5 ConstraintsModel accessors each
  restrict 4 params to `<:Tuple` in the type-parameter position but extracted
  the 5th via a bare `where`; now repeat `<:Tuple` on it.

Also applies the audit's two optional hardening bounds, verified safe against
every construction site (production and tests):

- CoercedTrajectory: `coerce::C` is always `only`/`identity`
  → `C<:Union{typeof(only),typeof(identity)}`.
- MergedTrajectory: `comps::C` is always `Dict{Int,Function}`
  → `C<:AbstractDict{Int,<:Function}` (struct + both hand-written constructors).

Adds a regression guard (test/suite/meta/test_where_bounds.jl) asserting each
fixed method's induced TypeVar upper bound is the intended bound, not `Any`.

Full suite: 4014/4014 pass (incl. Aqua ambiguity checks). No behaviour change —
tightening a where/field bound cannot invalidate a previously-valid call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ocots
ocots merged commit 8e5d555 into main Jul 12, 2026
4 checks passed
@ocots
ocots deleted the fix/where-bound-drops branch July 12, 2026 21:21
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