Skip to content

Commit

Permalink
Merge pull request #302 from fxn/cnames
Browse files Browse the repository at this point in the history
Micro-optimization for explicit namespaces
  • Loading branch information
fxn authored Oct 12, 2024
2 parents b6fe78b + e0695a7 commit c6152fb
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 24 deletions.
3 changes: 3 additions & 0 deletions lib/zeitwerk/cref.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
class Zeitwerk::Cref
include Zeitwerk::RealModName

# @sig Module
attr_reader :mod

# @sig Symbol
attr_reader :cname

Expand Down
36 changes: 22 additions & 14 deletions lib/zeitwerk/explicit_namespace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,56 @@ module Zeitwerk
# Loaders that reopen namespaces owned by other projects are responsible for
# loading their constant before setup. This is documented.
module ExplicitNamespace # :nodoc: all
# Maps cpaths of explicit namespaces with their corresponding loader.
# Entries are added as the namespaces are found, and removed as they are
# autoloaded.
# Maps cnames or cpaths of explicit namespaces with their corresponding
# loader. They are symbols if the namespace lives in Object, or qualified
# paths as strings otherwise. Entries are added as the namespaces are found,
# and removed as they are autoloaded.
#
# @sig Hash[String => Zeitwerk::Loader]
@cpaths = {}
# @sig Hash[(Symbol | String) => Zeitwerk::Loader]
@loaders = {}

class << self
include RealModName
extend Internal

# Registers `cpath` as being the constant path of an explicit namespace
# Registers `cref` as being the constant path of an explicit namespace
# managed by `loader`.
#
# @sig (String, Zeitwerk::Loader) -> void
internal def register(cpath, loader)
@cpaths[cpath] = loader
internal def register(cref, loader)
if Object.equal?(cref.mod)
@loaders[cref.cname] = loader
else
@loaders[cref.path] = loader
end
end

# @sig (String) -> Zeitwerk::Loader?
internal def loader_for(mod, cname)
cpath = Object.equal?(mod) ? cname.name : "#{real_mod_name(mod)}::#{cname}"
@cpaths.delete(cpath)
if Object.equal?(mod)
@loaders.delete(cname)
else
@loaders.delete("#{real_mod_name(mod)}::#{cname}")
end
end

# @sig (Zeitwerk::Loader) -> void
internal def unregister_loader(loader)
@cpaths.delete_if { _2.equal?(loader) }
@loaders.delete_if { _2.equal?(loader) }
end

# This is an internal method only used by the test suite.
#
# @sig (String) -> Zeitwerk::Loader?
internal def registered?(cpath)
@cpaths[cpath]
internal def registered?(cname_or_cpath)
@loaders[cname_or_cpath]
end

# This is an internal method only used by the test suite.
#
# @sig () -> void
internal def clear
@cpaths.clear
@loaders.clear
end

module Synchronized
Expand Down
2 changes: 1 addition & 1 deletion lib/zeitwerk/loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ def all_dirs

# @sig (Zeitwerk::Cref) -> void
private def register_explicit_namespace(cref)
ExplicitNamespace.__register(cref.path, self)
ExplicitNamespace.__register(cref, self)
end

# @sig (String) -> void
Expand Down
2 changes: 1 addition & 1 deletion test/lib/zeitwerk/test_autovivification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ module Namespace; end
["rd2/admin/y.rb", "Admin::Y = true"]
]
with_setup(files) do
assert !Zeitwerk::ExplicitNamespace.__registered?("Admin")
assert !Zeitwerk::ExplicitNamespace.__registered?(:Admin)
end
end

Expand Down
8 changes: 4 additions & 4 deletions test/lib/zeitwerk/test_unload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,14 @@ def self.name
]
with_files(files) do
la = new_loader(dirs: "a")
assert Zeitwerk::ExplicitNamespace.__registered?("M") == la
assert Zeitwerk::ExplicitNamespace.__registered?(:M) == la

lb = new_loader(dirs: "b")
assert Zeitwerk::ExplicitNamespace.__registered?("X") == lb
assert Zeitwerk::ExplicitNamespace.__registered?(:X) == lb

la.unload
assert_nil Zeitwerk::ExplicitNamespace.__registered?("M")
assert Zeitwerk::ExplicitNamespace.__registered?("X") == lb
assert_nil Zeitwerk::ExplicitNamespace.__registered?(:M)
assert Zeitwerk::ExplicitNamespace.__registered?(:X) == lb
end
end

Expand Down
8 changes: 4 additions & 4 deletions test/lib/zeitwerk/test_unregister.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,29 @@ class TestUnregister < LoaderTest
registry.gem_loaders_by_root_file["dummy1"] = loader1
registry.register_autoload(loader1, "dummy1")
registry.register_inception("dummy1", "dummy1", loader1)
Zeitwerk::ExplicitNamespace.__register("dummy1", loader1)
Zeitwerk::ExplicitNamespace.__register(Zeitwerk::Cref.new(Object, :"dummy"), loader1)

loader2 = Zeitwerk::Loader.new
registry = Zeitwerk::Registry
registry.register_loader(loader2)
registry.gem_loaders_by_root_file["dummy2"] = loader2
registry.register_autoload(loader2, "dummy2")
registry.register_inception("dummy2", "dummy2", loader2)
Zeitwerk::ExplicitNamespace.__register("dummy2", loader2)
Zeitwerk::ExplicitNamespace.__register(Zeitwerk::Cref.new(Object, :"dummy2"), loader2)

loader1.unregister

assert !registry.loaders.include?(loader1)
assert !registry.gem_loaders_by_root_file.values.include?(loader1)
assert !registry.autoloads.values.include?(loader1)
assert !registry.inceptions.values.any? {|_, l| l == loader1}
assert !Zeitwerk::ExplicitNamespace.__registered?("dummy1")
assert !Zeitwerk::ExplicitNamespace.__registered?(:"dummy1")

assert registry.loaders.include?(loader2)
assert registry.gem_loaders_by_root_file.values.include?(loader2)
assert registry.autoloads.values.include?(loader2)
assert registry.inceptions.values.any? {|_, l| l == loader2}
assert Zeitwerk::ExplicitNamespace.__registered?("dummy2") == loader2
assert Zeitwerk::ExplicitNamespace.__registered?(:"dummy2") == loader2
end

test 'with_loader yields and unregisters' do
Expand Down

0 comments on commit c6152fb

Please sign in to comment.