Skip to content

feat(extractor): recognise Behavior.AssociatedObject as a self-owned source (#227)#237

Merged
PhysShell merged 3 commits into
mainfrom
claude/associatedobject-227
Jul 12, 2026
Merged

feat(extractor): recognise Behavior.AssociatedObject as a self-owned source (#227)#237
PhysShell merged 3 commits into
mainfrom
claude/associatedobject-227

Conversation

@PhysShell

Copy link
Copy Markdown
Owner

Что и зачем

Расширяет self-owned-source исключение на sub-issue #227 (часть a родительского #221): Behavior-класс, подписывающийся на (элемент, достижимый из) собственного базового аксессора AssociatedObject, больше не помечается OWN001. Поведение не может пережить прикрепление, поэтому источник co-lifetimed с подписчиком — тот же собираемый GC цикл source<->this, что уже кодирует исключение для сконструированного поля, только достигнутый через аксессор базового класса (FP из MahApps TiltBehavior.cs, найденный oracle-sweep по #201).

Реализовано строго в зафиксированных «Design guardrails» issue, без расширения: гейт подписчика — база Behavior (IsBehaviorSubscriber, синтаксическое совпадение прямой базы по простому имени, как IsProcessLivedApplication); гейт источника — приёмник += разрешается, в пределах класса и по цепочке присваиваний, в this.AssociatedObject (ResolvesToAssociatedObject): напрямую, через var x = .../is-паттерн локал (только если не переприсвоен), либо через поле, каждое присваивание которому разрешается в AssociatedObject (единственная инъекция отменяет доказательство). Без межпроцедурных догадок. Lambda-обработчик здесь допустим (в отличие от #228): источник co-lifetimed с поведением.

Тип изменения

  • feat — новая возможность

Как проверено

  • python tests/run_tests.py (276/276 ownir, все зелёные)
  • ruff check . и mypy (чисто)
  • Реальный экстрактор (.NET 8) на AssociatedObjectSourceSample.cs: 3 тихих позитива (is-паттерн поле, прямой this.AssociatedObject.Event, bare-identifier локал), 4 контроля помечены (неродственный инъектированный источник в том же OnAttached; не-Behavior подписчик; поле, также получающее инъекцию; локал, переприсвоенный до +=)
  • Диф вывода экстрактора old vs new по всему набору сэмплов — байт-в-байт идентичен (ноль регрессий); все ассерты wpf-extractor CI проходят в обе стороны

Связанные issue

Closes #227. Refs #221 (родитель, контекст). Sub-issue #229 (часть c) идёт отдельным PR.

Чеклист

  • изменение покрыто CI-ассертами (AssociatedObjectSourceSample.cs + wpf-extractor job, обе стороны) и задокументировано в docs/notes/field-notes-patterns.md (entry 15, shape a)
  • README/docs обновлены при необходимости (field-notes entry 15 помечен как shipped для shape a)
  • коммиты в conventional-commit стиле (feat:)

🤖 Generated with Claude Code


Generated by Claude Code

…source (#227)

A `Behavior`-derived class subscribing to (an element reached from) its own
base-class `AssociatedObject` accessor was tiered "injected" and warned OWN001
(the MahApps TiltBehavior FP from the issue #201 sweep). A behavior is attached
to and detached from exactly one element and cannot outlive being attached, so
that element is co-lifetimed with the subscriber — the same collectable
source<->this cycle the shipped self-owned-source exemption already encodes for a
constructed field, just reached through a base-class accessor.

The exemption is deliberately narrow, within the issue's fixed guardrails:
- subscriber gate: the class must derive from `Behavior` (`IsBehaviorSubscriber`,
  direct-base simple-name match, mirroring `IsProcessLivedApplication`) — the
  attach/detach pairing is what guarantees co-lifetime;
- source gate: the `+=` receiver must resolve, same-class and assignment-chain-
  local, to `this.AssociatedObject` (`ResolvesToAssociatedObject`) — directly, via
  a `var x = ...`/`is`-pattern local (only when never reassigned), or via a field
  every assignment to which resolves to `AssociatedObject` (a single injected write
  denies the proof). No interprocedural guessing.

Unlike #228 a lambda handler is fine here: the source is co-lifetimed with the
behavior, so a capture just closes the collectable cycle rather than pinning a
process-lived source to a shorter-lived local.

Pinned by AssociatedObjectSourceSample.cs: three silent positives (is-pattern
field, direct `this.AssociatedObject.Event`, bare-identifier local) and the four
controls the guardrails require — the unrelated injected source in the same
OnAttached, a non-Behavior subscriber, a field also assigned an injected value,
and a resolver-bound local reassigned before the `+=` — wired into the
wpf-extractor CI job with assertions both ways.

Verified locally with the real extractor (.NET 8): the sample yields exactly the
four control warnings, all three positives silent; a full-sample-set diff of old
vs new extractor output is byte-identical (zero regression). Gates: run_tests
276/276, ruff, mypy, yaml all green.

Closes #227

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2AWb9N2VUsby2XpdftBcu
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@PhysShell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ef2ac22d-2ecb-482c-b0c3-0ed8ce0f2ca5

📥 Commits

Reviewing files that changed from the base of the PR and between b1ee961 and e0f0867.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • docs/notes/field-notes-patterns.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • frontend/roslyn/samples/AssociatedObjectSourceSample.cs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/associatedobject-227

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

if (_attached is Panel panel)
panel.Loaded += (s, e) => Handle(); // silent (#227): panel IS AssociatedObject

_bus.Changed += (s, e) => Handle(); // OWN001: unrelated injected source

public void Wire()
{
this.AssociatedObject.Loaded += OnLoaded; // OWN001: subscriber is not a Behavior
{
_el = this.AssociatedObject;
if (_el is Panel panel)
panel.Loaded += OnLoaded; // OWN001: _el is also injected below
Comment thread frontend/roslyn/samples/AssociatedObjectSourceSample.cs Fixed

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa1af42e7b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1052 to +1054
MemberAccessExpressionSyntax m => m.Name.Identifier.Text == "AssociatedObject"
&& m.Expression is ThisExpressionSyntax,
IdentifierNameSyntax id => id.Identifier.Text == "AssociatedObject",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve AssociatedObject before exempting it

In a Behavior subclass, a local/parameter or a hidden member named AssociatedObject can refer to an injected publisher rather than the inherited behavior accessor (for example void Wire(UiElement AssociatedObject) { AssociatedObject.Loaded += OnLoaded; }). Because this branch accepts the identifier purely by text before checking GetSymbolInfo, IsAssociatedObjectSource will suppress the new subscription warning even though the source is not co-lifetimed with the behavior.

Useful? React with 👍 / 👎.

#227, Codex P2)

IsAssociatedObjectAccess matched the identifier `AssociatedObject` purely by text,
so a local, a parameter, or a hidden member DECLARED in the Behavior subclass named
`AssociatedObject` — holding an injected publisher, not the inherited accessor —
would wrongly pass the self-owned-source gate (`void Wire(UiElement AssociatedObject)
{ AssociatedObject.Loaded += H; }`). That source is not co-lifetimed with the
behavior, so the subscription must stay flagged.

The name match now consults the symbol: the genuine base accessor is either
UNRESOLVED (null — the Interactivity assembly is absent on the runner, the normal
WPF case) or an INHERITED member (containing type is a base, not this class); a
local/parameter binding, or a member declared on this class, is a shadow and denies
the exemption. Conservative — an unresolvable symbol keeps today's behaviour.

New flagged control ShadowParamBehavior (a parameter named AssociatedObject) +
a CI assertion. Verified with the real extractor: all five controls warn, the three
positives stay silent; full-sample-set output still byte-identical to main.

Refs #227

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2AWb9N2VUsby2XpdftBcu
var src = this.AssociatedObject;
src = _injected; // rebind -> declaration binding stale
if (src is { } s0)
s0.Loaded += OnLoaded; // OWN001: reassigned local
{
public void Wire(UiElement AssociatedObject)
{
AssociatedObject.Loaded += OnLoaded; // OWN001: shadowing parameter
…ct field proof (#227)

FieldAssignedOnlyFromAssociatedObject walked only the one class declaration holding
the `+=`, so for a `partial` Behavior a disqualifying injected write to the field in
a sibling partial FILE was invisible — the field could pass as an AssociatedObject
alias while actually being injected elsewhere (same class of hole CodeRabbit flagged
on the #229 sibling). It now scans every partial declaration of the field's
containing type via the symbol's DeclaringSyntaxReferences (the merged compilation
makes them all reachable), each with its own tree's semantic model.

New flagged control PartialFieldBehavior (field assigned from AssociatedObject in one
partial, from an injected value in the other) + a CI assertion. Verified with the
real extractor: all six controls warn, the three positives stay silent;
full-sample-set output byte-identical to main. Gates: run_tests 276/276, ruff, mypy,
yaml all green.

Refs #227

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2AWb9N2VUsby2XpdftBcu
{
_el = this.AssociatedObject;
if (_el is Panel panel)
panel.Loaded += OnLoaded; // OWN001: _el also injected in the sibling partial
@github-actions

Copy link
Copy Markdown

@coderabbitai review

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.

Self-owned source (a): Behavior.AssociatedObject as a self-owned reference

3 participants