Summary
clear_extra_constants is supposed to preserve constants introduced by require calls during example evaluation, but it fails for constants defined in C extensions (.so/.bundle files). Module#const_source_location returns [false, 0] for C-defined constants. Because false is falsy, the guard source_file && skip_if_loaded_by.include?(source_file) short-circuits and the constant is removed.
On subsequent examples, require is a no-op (the file is already in $LOADED_FEATURES), so the constant stays gone, causing NameError.
Location
lib/yard_example_test/example/constant_sandbox.rb — clear_extra_constants
def clear_extra_constants(scope, before, skip_if_loaded_by: [])
(scope.constants - before).each do |constant|
if skip_if_loaded_by.any?
source_file, = scope.const_source_location(constant.to_s)
next if source_file && skip_if_loaded_by.include?(source_file)
end
scope.__send__(:remove_const, constant)
end
end
const_source_location return values
| Constant type |
Return value |
source_file |
| Ruby source file |
["/path/to/file.rb", 10] |
"/path/to/file.rb" (truthy) |
| C extension |
[false, 0] |
false (falsy) |
| Autoloaded (not yet loaded) |
[nil, nil] |
nil (falsy) |
| Built-in |
nil |
N/A (destructure gives nil) |
Concrete example
# @example first
# require 'json'
# JSON.parse('{}') #=> {}
#
# @example second
# JSON.parse('[]') #=> [] # NameError: uninitialized constant JSON
After the first example, JSON (defined by a C extension) is removed from Object even though json was loaded via require. The second example fails because require 'json' is a no-op (already in $LOADED_FEATURES) and JSON is gone.
Proposed solution
Treat false from const_source_location as "defined in a native extension" and preserve the constant. One approach:
def clear_extra_constants(scope, before, skip_if_loaded_by: [])
(scope.constants - before).each do |constant|
if skip_if_loaded_by.any?
location = scope.const_source_location(constant.to_s)
source_file = location&.first
# false means C extension — preserve it; nil/missing means dynamic
next if source_file == false
next if source_file && skip_if_loaded_by.include?(source_file)
end
scope.__send__(:remove_const, constant)
end
end
Acceptance criteria
Summary
clear_extra_constantsis supposed to preserve constants introduced byrequirecalls during example evaluation, but it fails for constants defined in C extensions (.so/.bundlefiles).Module#const_source_locationreturns[false, 0]for C-defined constants. Becausefalseis falsy, the guardsource_file && skip_if_loaded_by.include?(source_file)short-circuits and the constant is removed.On subsequent examples,
requireis a no-op (the file is already in$LOADED_FEATURES), so the constant stays gone, causingNameError.Location
lib/yard_example_test/example/constant_sandbox.rb—clear_extra_constantsconst_source_locationreturn valuessource_file["/path/to/file.rb", 10]"/path/to/file.rb"(truthy)[false, 0]false(falsy)[nil, nil]nil(falsy)nilnil)Concrete example
After the first example,
JSON(defined by a C extension) is removed fromObjecteven thoughjsonwas loaded viarequire. The second example fails becauserequire 'json'is a no-op (already in$LOADED_FEATURES) andJSONis gone.Proposed solution
Treat
falsefromconst_source_locationas "defined in a native extension" and preserve the constant. One approach:Acceptance criteria
requireduring example evaluation are preserved across examplesrequireare still preserved (existing behavior)require) are still cleaned up