Skip to content

Commit c9b056a

Browse files
committed
[Kernel] Add Kernel.trace_var and Kernel.untrace_var
1 parent 4482ed2 commit c9b056a

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
@@ -2203,6 +2203,49 @@ module Kernel : BasicObject
22032203
def self?.system: (String command, *String args, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: redirect_fd, ?out: redirect_fd, ?err: redirect_fd, ?close_others: boolish, ?chdir: String, ?exception: bool) -> (NilClass | FalseClass | TrueClass)
22042204
| (Hash[string, string?] env, String command, *String args, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: redirect_fd, ?out: redirect_fd, ?err: redirect_fd, ?close_others: boolish, ?chdir: String, ?exception: bool) -> (NilClass | FalseClass | TrueClass)
22052205

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

test/stdlib/Kernel_test.rb

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

117192
class KernelInstanceTest < Test::Unit::TestCase

0 commit comments

Comments
 (0)