diff --git a/lib/rbs/definition_builder/method_builder.rb b/lib/rbs/definition_builder/method_builder.rb index 530a9d208..2dc3a706b 100644 --- a/lib/rbs/definition_builder/method_builder.rb +++ b/lib/rbs/definition_builder/method_builder.rb @@ -46,40 +46,47 @@ def validate! self end - def each - if block_given? - Sorter.new(methods).each_strongly_connected_component do |scc| - if scc.size > 1 - raise RecursiveAliasDefinitionError.new(type: type, defs: scc) + def each(&block) + if block + # Yields the original method of an alias before the alias, like the + # topological sort did, and detects recursive alias definitions on the way + if methods.each_value.any? {|defn| defn.original.is_a?(AST::Members::Alias) } + done = {} #: Hash[Definition, bool] + done.compare_by_identity + methods.each_value do |defn| + each_alias_first(defn, done, [], &block) end - - yield scc[0] + else + methods.each_value(&block) end else enum_for :each end end - class Sorter - include TSort + private - attr_reader :methods + def each_alias_first(defn, done, visiting, &block) + return if done[defn] - def initialize(methods) - @methods = methods + if visiting.any? {|other| other.equal?(defn) } + index = visiting.index {|other| other.equal?(defn) } or raise + raise RecursiveAliasDefinitionError.new(type: type, defs: visiting[index..] || raise) end - def tsort_each_node(&block) - methods.each_value(&block) - end - - def tsort_each_child(defn) - if (member = defn.original).is_a?(AST::Members::Alias) - if old = methods[member.old_name] - yield old + if (member = defn.original).is_a?(AST::Members::Alias) + if old = methods.fetch(member.old_name, nil) + # A self alias forms a size-1 SCC that the topological sort yielded as is + unless old.equal?(defn) + visiting.push(defn) + each_alias_first(old, done, visiting, &block) + visiting.pop end end end + + done[defn] = true + yield defn end end diff --git a/sig/method_builder.rbs b/sig/method_builder.rbs index c9531ac30..e9bef0859 100644 --- a/sig/method_builder.rbs +++ b/sig/method_builder.rbs @@ -47,17 +47,14 @@ module RBS def each: () { (Definition) -> void } -> void | () -> Enumerator[Definition, void] - class Sorter - include TSort[Definition] + private - attr_reader methods: Hash[Symbol, Definition] - - def initialize: (Hash[Symbol, Definition]) -> void - - def tsort_each_node: { (Definition) -> void } -> void - - def tsort_each_child: (Definition) { (Definition) -> void } -> void - end + # Yields the definition, recursively yielding the original of an alias first + # + # Raises `RecursiveAliasDefinitionError` when the aliases form a cycle of two or + # more methods. + # + def each_alias_first: (Definition, Hash[Definition, bool] done, Array[Definition] visiting) { (Definition) -> void } -> void end attr_reader env: Environment