Skip to content

Make S7 generics and methods traceable - #695

Merged
t-kalinowski merged 9 commits into
mainfrom
make-traceable
Oct 2, 2026
Merged

t-kalinowski merged 9 commits into
mainfrom
make-traceable

Conversation

@hadley

@hadley hadley commented Jun 10, 2026 •

Copy link
Copy Markdown
Member

Fixes #584.

Allow trace() and untrace() to work with S7 generics and methods, including tracing both during multiple dispatch. For example, trace("my_class", browser, where = my_generic@methods) sets a breakpoint in the method for my_class. Traced functions retain their S7 properties and representation metadata, so method lookup, property access, and printing continue to work. untrace() restores the original function.

Internally, this declares the S7 generic and method properties as S4 slots, exposes the .Data slot needed by R's tracing initializer, and recognizes traced generics during native dispatch and method lookup. It moves methods from Suggests to Imports so registration can happen directly during package loading.

R versions before 4.7 use a compatibility initializer that preserves S7 metadata. On R 4.7 and later, S7 uses R's initializer, incorporating the upstream fixes discussed in r-devel/r-svn#262. Those fixes complement the slot declarations and dispatch changes here.

Local measurements on macOS with R 4.6.1, comparing this PR with main (245aaf46):

  • Untraced single and multiple dispatch stayed at about 1.8 µs and 2.6 µs, respectively, before and after the change (1.0× faster, within measurement noise), with no measured allocation.
  • Loading S7 increased from about 11 ms to 45 ms with methods already loaded (main loads 4.3× faster). This adds about 35 ms once per fresh session for the S4 registrations.
  • With an empty tracer attached, single dispatch increased from 1.8 µs to 3.1 µs for a traced method or 3.4 µs for a traced generic (untraced dispatch is 1.7× or 1.9× faster). These costs apply only while tracing is active.

The dispatch and tracing measurements can be reproduced with bench/method-trace.R against separately installed builds.

This is pure claude, and appears to fix the problem, but if it's really about class returning a vector instead of a single string, it's likely we should upstream this to R itself.

Fixes #584
Register generic and method properties on their S4 old classes and keep
the initializer workaround for R versions before 4.7. Share native
normalization across dispatch and method lookup while preserving the
ordinary dispatch path.

Support the data slot read by R tracing and import the S4 initializer for
namespace loading. Add public API regressions and benchmarks for dispatch
and trace setup.
Register the representation version slot and prototype for S7 generics
and methods so tracing preserves the marker introduced on main.
Cover the marker before and during tracing through the public API.
Register S7's S4 classes and tracing methods when methods loads, using
a writable registry exposed through namespace class bindings. Preserve
registration metadata and R's tracing caches across S7 unloads and reloads.

Use base S3 group definitions directly so ordinary S7 use does not load
methods. Cover both namespace load orders, reloads, and tracing restoration.
Import methods and register S7 S4 classes directly during package load.
Remove the deferred hook, separate registry, active bindings, and custom
cache cleanup, and restore group-generic discovery through methods.

Keep the pre-R 4.7 tracing initializer and representation version slots.
Update base-only loading and reload tests for the imported dependency.
Restore static declarations for the two base S4 classes and register each
S7 function class when it is defined. Remove the separate registration
hook and environment arguments left over from deferred methods loading.

Consolidate the base-only loading, reload, and unload checks into one
subprocess test while retaining the focused tracing and metadata tests.
@t-kalinowski t-kalinowski changed the title Make methods traceable Make S7 generics and methods traceable Oct 1, 2026
@hadley

hadley commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

Could you give us a brief (AI generated) summary of why all this additional machinery is necessary?

@t-kalinowski

Copy link
Copy Markdown
Member

trace() replaces an S7 function with an S4 wrapper. The additional code keeps that wrapper usable as an S7 generic or method:

  • Preserve S7 metadata. Register the properties and internal class/version attributes as S4 slots, following [Michael’s suggestion](Make trace() work for functions whose S3 class has multiple strings r-devel/r-svn#262 (comment)). This lets R’s tracing machinery preserve them. The .Data change lets R read the underlying function through S7’s @.
  • Support method lookup as well as dispatch. The original PR unwrapped traced generics when calling them, but method() needed the same treatment. R’s C-level inheritance check doesn’t recognize the wrapper’s S4 inheritance.
  • Support older R versions. The custom initializer handles the class-vector and coercion bugs before R 4.7. On R 4.7+, we use R’s initializer; the slot declarations and dispatch changes are still needed.

Importing methods lets us register everything directly, without deferred-loading hooks. Most of the added lines are regression tests and benchmarks.

@hadley

hadley commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

I can't approve my own PR but I think this is good to go.

@t-kalinowski
t-kalinowski merged commit 9e9c9d0 into main Oct 2, 2026
15 checks passed
@t-kalinowski
t-kalinowski deleted the make-traceable branch October 2, 2026 16:08
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.

S7 methods defined for S7 classes cannot be traced

2 participants