Skip to content

Commit 6625598

Browse files
authored
Merge pull request #2682 from sampersand/sampersand/2025-10-13/add-tracevar
[Kernel] Add Kernel.trace_var and Kernel.untrace_var
2 parents 5771976 + 9bb0bef commit 6625598

File tree

2 files changed

+118
-0
lines changed

2 files changed

+118
-0
lines changed

core/kernel.rbs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2214,6 +2214,49 @@ module Kernel : BasicObject
22142214
def self?.system: (String command, *String args, ?unsetenv_others: bool, ?pgroup: true | Integer, ?umask: Integer, ?in: redirect_fd, ?out: redirect_fd, ?err: redirect_fd, ?close_others: bool, ?chdir: String, ?exception: bool) -> (NilClass | FalseClass | TrueClass)
22152215
| (Hash[string, string?] env, String command, *String args, ?unsetenv_others: bool, ?pgroup: true | Integer, ?umask: Integer, ?in: redirect_fd, ?out: redirect_fd, ?err: redirect_fd, ?close_others: bool, ?chdir: String, ?exception: bool) -> (NilClass | FalseClass | TrueClass)
22162216

2217+
# An interface used with `trace_var` (and `untrace_var`) for custom command types.
2218+
interface _Tracer
2219+
# Called whenever the global variable that's being traced changes; the argument is the new value.
2220+
def call: (untyped argument) -> void
2221+
end
2222+
2223+
# <!--
2224+
# rdoc-file=eval.c
2225+
# - trace_var(symbol, cmd ) -> nil
2226+
# - trace_var(symbol) {|val| block } -> nil
2227+
# -->
2228+
# Controls tracing of assignments to global variables. The parameter `symbol`
2229+
# identifies the variable (as either a string name or a symbol identifier).
2230+
# *cmd* (which may be a string or a `Proc` object) or block is executed whenever
2231+
# the variable is assigned. The block or `Proc` object receives the variable's
2232+
# new value as a parameter. Also see #untrace_var.
2233+
#
2234+
# trace_var :$_, proc {|v| puts "$_ is now '#{v}'" }
2235+
# $_ = "hello"
2236+
# $_ = ' there'
2237+
#
2238+
# *produces:*
2239+
#
2240+
# $_ is now 'hello'
2241+
# $_ is now ' there'
2242+
#
2243+
def self?.trace_var: (interned name, String | _Tracer cmd) -> nil
2244+
| (interned name) { (untyped value) -> void } -> nil
2245+
| (interned name, nil) -> Array[String | _Tracer]?
2246+
2247+
# <!--
2248+
# rdoc-file=eval.c
2249+
# - untrace_var(symbol [, cmd] ) -> array or nil
2250+
# -->
2251+
# Removes tracing for the specified command on the given global variable and
2252+
# returns `nil`. If no command is specified, removes all tracing for that
2253+
# variable and returns an array containing the commands actually removed.
2254+
#
2255+
def self?.untrace_var: (interned name, ?nil) -> Array[String | _Tracer]
2256+
| (interned name, String cmd) -> [String]?
2257+
| [T < _Tracer] (interned name, T cmd) -> [T]?
2258+
| (interned name, untyped cmd) -> nil
2259+
22172260
# <!--
22182261
# rdoc-file=object.c
22192262
# - obj !~ other -> true or false

test/stdlib/Kernel_test.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,81 @@ def test_rand
126126
assert_send_type "(Range[Float]) -> Float", Kernel, :rand, 0.0...10.0
127127
assert_send_type "(Range[Float]) -> nil", Kernel, :rand, 0.0...0.0
128128
end
129+
130+
def test_trace_var
131+
tracer = BlankSlate.new
132+
def tracer.call(new) nil end
133+
134+
with_interned '$__TEST_TRACE_VAR' do |name|
135+
assert_send_type '(interned, String) -> nil',
136+
Kernel, :trace_var, name, '1'
137+
assert_send_type '(interned, ::Kernel::_Tracer) -> nil',
138+
Kernel, :trace_var, name, tracer
139+
assert_send_type '(interned) { (any) -> void } -> nil',
140+
Kernel, :trace_var, name do |x| 0 end
141+
142+
# `Kernel.trace_var` doesn't actually check the type of its second argument,
143+
# but instead defers until the global is actually assigned. To ensure that
144+
# our signatures are correct, we assign the global here (which, if our
145+
# signatures are incorrect, will raise an exception)
146+
$__TEST_TRACE_VAR = 1
147+
148+
# Acts the same as `untrace_var`, so this performs the untracing for us.
149+
assert_send_type '(interned, nil) -> Array[String | ::Kernel::_Tracer]',
150+
Kernel, :trace_var, name, nil
151+
end
152+
ensure
153+
# Just in case an exception stopped it, we don't want to continue tracing.
154+
# We do `defined?` as `untrace_var :$some_undefined_global` fails
155+
untrace_var :$__TEST_TRACE_VAR if defined? $__TEST_TRACE_VAR
156+
end
157+
158+
def test_untrace_var
159+
tracer = BlankSlate.new
160+
def tracer.call(new) nil end
161+
162+
with_interned '$__TEST_UNTRACE_VAR' do |name|
163+
# No argument yields all traces
164+
trace_var :$__TEST_UNTRACE_VAR, '"string"'
165+
trace_var :$__TEST_UNTRACE_VAR do "proc" end
166+
trace_var :$__TEST_UNTRACE_VAR, tracer
167+
assert_send_type '(interned) -> Array[String | ::Kernel::_Tracer]',
168+
Kernel, :untrace_var, name
169+
170+
# `nil` also yields all traces
171+
trace_var :$__TEST_UNTRACE_VAR, '"string"'
172+
trace_var :$__TEST_UNTRACE_VAR do "proc" end
173+
trace_var :$__TEST_UNTRACE_VAR, tracer
174+
assert_send_type '(interned, nil) -> Array[String | ::Kernel::_Tracer]',
175+
Kernel, :untrace_var, name, nil
176+
177+
# Passing a String in yields the string if they're the same, or `nil`
178+
string = '"string"'
179+
trace_var :$__TEST_UNTRACE_VAR, string
180+
assert_send_type '(interned, String) -> [String]',
181+
Kernel, :untrace_var, name, string
182+
assert_send_type '(interned, String) -> nil',
183+
Kernel, :untrace_var, name, 'not a trace'
184+
185+
# Passing a `tracer` yields the tracer if it's set, or `nil` otherwise
186+
trace_var :$__TEST_UNTRACE_VAR, tracer
187+
assert_send_type '[T < ::Kernel::_Tracer] (interned, T) -> [T]',
188+
Kernel, :untrace_var, name, tracer
189+
assert_send_type '[T < ::Kernel::_Tracer] (interned, T) -> nil',
190+
Kernel, :untrace_var, name, tracer
191+
192+
# Anything else is `nil`
193+
with_untyped do |trace|
194+
next if nil == trace
195+
assert_send_type '(interned, untyped) -> nil',
196+
Kernel, :untrace_var, name, trace
197+
end
198+
end
199+
ensure
200+
# Just in case an exception stopped it, we don't want to continue tracing.
201+
# We do `defined?` as `untrace_var :$some_undefined_global` fails
202+
untrace_var :$__TEST_UNTRACE_VAR if defined? $__TEST_UNTRACE_VAR
203+
end
129204
end
130205

131206
class KernelInstanceTest < Test::Unit::TestCase

0 commit comments

Comments
 (0)