Skip to content

Commit e4f339e

Browse files
committed
Fix false self-referential mixin when bare name matches ancestor namespace
When a module like `A::Enumerable` contains `include Enumerable`, YARD's lexical resolver would walk up to parent namespace `A` and find `A::Enumerable` as the match for the bare name `Enumerable`. This caused the module to be recorded as including itself, producing incorrect documentation and potential infinite recursion in `inheritance_tree`. Fix: in `RegistryResolver#lookup_by_path`, skip a direct-lookup result that resolves back to the original starting namespace when the search has already moved to a parent namespace. This prevents circular resolution while leaving all other lookups (including from root) unaffected. Fixes #1116 (the valid-Ruby case from #1116 (comment))
1 parent 1e6d517 commit e4f339e

3 files changed

Lines changed: 28 additions & 0 deletions

File tree

lib/yard/registry_resolver.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ def lookup_by_path(path, opts = {})
7575
lexical_lookup = 0
7676
while namespace && !resolved
7777
resolved = lookup_path_direct(namespace, path, type)
78+
# Prevent a bare name from resolving back to the namespace we started
79+
# from when searching through a parent namespace. For example,
80+
# `include Enumerable` inside `A::Enumerable` would walk up to namespace
81+
# `A` and match `A::Enumerable`, creating a false self-referential mixin.
82+
# Only skip when we have already moved to a parent (namespace != orignamespace).
83+
# See https://github.com/lsegal/yard/issues/1116
84+
resolved = nil if resolved.equal?(orignamespace) && !namespace.equal?(orignamespace)
7885
resolved ||= lookup_path_inherited(namespace, path, type) if inheritance
7986
break if resolved
8087
namespace = namespace.parent

spec/handlers/examples/mixin_handler_001.rb.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ end
3939
module FromConstant; end
4040
FromConstant.include A
4141

42+
module Outer1
43+
end
44+
45+
module Outer1::Inner
46+
include Inner
47+
end
48+
4249
module Foo
4350
end
4451

spec/handlers/mixin_handler_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@
4545
expect(P("ABC::DEF::BAR").mixins).to eq [P("ABC::BAR")]
4646
end
4747

48+
it "does not create self-referential mixin when bare name matches an ancestor namespace" do
49+
# `include Inner` inside `Outer1::Inner` should not resolve to `Outer1::Inner` itself.
50+
# Previously YARD would walk up to namespace `Outer1` and find `Outer1::Inner`,
51+
# producing a false cyclic mixin. See https://github.com/lsegal/yard/issues/1116
52+
mod = P("Outer1::Inner")
53+
expect(mod.mixins.map(&:path)).not_to include("Outer1::Inner")
54+
expect(mod.mixins.map(&:path)).to eq ["Inner"]
55+
# inheritance_tree(true) should not recurse infinitely and should not
56+
# include Outer1::Inner as a *mixin* (it appears only as self at index 0)
57+
tree = mod.inheritance_tree(true)
58+
expect(tree.first).to eq mod
59+
expect(tree.drop(1).map(&:path)).not_to include("Outer1::Inner")
60+
end
61+
4862
it "raises undocumentable error if argument is variable" do
4963
undoc_error "module X; include invalid; end"
5064
expect(Registry.at('X').mixins).to eq []

0 commit comments

Comments
 (0)