Skip to content

Commit 6941eab

Browse files
committed
fix: 存在ガードの解決範囲を拡張 / Resolve loop and qualified guards
1 parent cb9c8e0 commit 6941eab

3 files changed

Lines changed: 61 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- Required options in eval mode accept space-separated lambda and unary expressions without consuming known option tokens as values.
1010
- Constant discovery now includes classes and modules assigned with `Class.new` / `Module.new` during the target file load.
1111
- Repeated loads retain assigned constant aliases when the source file is unchanged.
12-
- Repeated loads retain aliases from completed direct, multiple, and `const_set` assignments or matching fully qualified existence guards, including receivers found by Ruby's lexical constant fallback, without reviving failed assignments or aliases behind disabled conditions.
12+
- Repeated loads retain aliases from completed direct, multiple, and `const_set` assignments or matching fully qualified existence guards, including loop initializers and receivers found by Ruby's lexical constant fallback, without reviving failed assignments or aliases behind disabled conditions.
1313
- Constant discovery analyzes the preloaded source without triggering autoloads, so inactive autoload branches and self-removing target files do not add side effects or fail after a successful load.
1414
- Constructor arity errors raised by `--new` are now wrapped in Rubycli's user-facing runner error.
1515
- Framework argument errors raised by constructors are also wrapped in the same user-facing runner error.

lib/rubycli/constant_capture.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,10 @@ def guarded_condition(context)
447447
[context[1], true]
448448
when :unless_mod
449449
[context[1], false]
450+
when :while, :while_mod
451+
[context[1], true]
452+
when :until, :until_mod
453+
[context[1], false]
450454
when :ifop
451455
[context[2], context[1] == :then]
452456
when :binary
@@ -594,7 +598,7 @@ def constant_name_from_node(node, namespace)
594598
when :@const
595599
(namespace + [node[1]]).join('::')
596600
when :const_path_field, :const_path_ref
597-
parent_name = constant_name_from_node(node[1], namespace)
601+
parent_name = receiver_constant_name_from_node(node[1], namespace)
598602
child_name = node.dig(2, 1)
599603
[parent_name, child_name].compact.join('::')
600604
when :top_const_field, :top_const_ref

test/constant_capture_test.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,61 @@ module CaptureEquivalentGuardOwner; end
773773
end
774774
end
775775

776+
def test_retains_aliases_initialized_by_loop_existence_guards
777+
skip 'Ruby 2.x does not reliably emit loop-condition line events' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.0')
778+
779+
capture = Rubycli::ConstantCapture.new
780+
Tempfile.create(['loop_guarded_aliases', '.rb']) do |file|
781+
file.write(<<~RUBY)
782+
module CaptureLoopGuardSource
783+
def self.run; end
784+
end
785+
while !defined?(CaptureWhileGuardAlias)
786+
CaptureWhileGuardAlias = CaptureLoopGuardSource
787+
end
788+
CaptureUntilGuardAlias = CaptureLoopGuardSource until defined?(CaptureUntilGuardAlias)
789+
RUBY
790+
file.flush
791+
792+
2.times do
793+
capture_io { capture.capture(file.path) { load file.path } }
794+
assert_includes capture.constants_for(file.path), 'CaptureWhileGuardAlias'
795+
assert_includes capture.constants_for(file.path), 'CaptureUntilGuardAlias'
796+
end
797+
ensure
798+
cleanup_constant(:CaptureWhileGuardAlias)
799+
cleanup_constant(:CaptureUntilGuardAlias)
800+
cleanup_constant(:CaptureLoopGuardSource)
801+
end
802+
end
803+
804+
def test_retains_namespace_qualified_defined_guard
805+
capture = Rubycli::ConstantCapture.new
806+
Tempfile.create(['namespace_qualified_guard', '.rb']) do |file|
807+
file.write(<<~RUBY)
808+
module CaptureQualifiedGuardSource
809+
def self.run; end
810+
end
811+
module CaptureQualifiedGuardNamespace
812+
Runner = CaptureQualifiedGuardSource unless defined?(CaptureQualifiedGuardNamespace::Runner)
813+
end
814+
RUBY
815+
file.flush
816+
817+
2.times do
818+
capture_io { capture.capture(file.path) { load file.path } }
819+
assert_includes capture.constants_for(file.path), 'CaptureQualifiedGuardNamespace::Runner'
820+
end
821+
ensure
822+
if Object.const_defined?(:CaptureQualifiedGuardNamespace)
823+
owner = Object.const_get(:CaptureQualifiedGuardNamespace)
824+
owner.send(:remove_const, :Runner) if owner.const_defined?(:Runner, false)
825+
end
826+
cleanup_constant(:CaptureQualifiedGuardNamespace)
827+
cleanup_constant(:CaptureQualifiedGuardSource)
828+
end
829+
end
830+
776831
def test_does_not_retain_const_set_moved_into_uninvoked_method
777832
capture = Rubycli::ConstantCapture.new
778833
Tempfile.create(['deferred_const_set_alias', '.rb']) do |file|

0 commit comments

Comments
 (0)