lib/parlour/conflict_resolver.rb in parlour-2.1.0 vs lib/parlour/conflict_resolver.rb in parlour-3.0.0
- old
+ new
@@ -1,6 +1,8 @@
# typed: true
+require 'set'
+
module Parlour
# Responsible for resolving conflicts (that is, multiple definitions with the
# same name) between objects defined in the same namespace.
class ConflictResolver
extend T::Sig
@@ -40,12 +42,19 @@
# @return [void]
def resolve_conflicts(namespace, &resolver)
Debugging.debug_puts(self, Debugging::Tree.begin("Resolving conflicts for #{namespace.name}..."))
# Check for multiple definitions with the same name
- grouped_by_name_children = namespace.children.group_by(&:name)
-
+ # (Special case here: writer attributes get an "=" appended to their name)
+ grouped_by_name_children = namespace.children.group_by do |child|
+ if RbiGenerator::Attribute === child && child.kind == :writer
+ "#{child.name}=" unless child.name.end_with?('=')
+ else
+ child.name
+ end
+ end
+
grouped_by_name_children.each do |name, children|
Debugging.debug_puts(self, Debugging::Tree.begin("Checking children named #{name}..."))
if children.length > 1
Debugging.debug_puts(self, Debugging::Tree.here("Possible conflict between #{children.length} objects"))
@@ -58,10 +67,24 @@
Debugging.debug_puts(self, Debugging::Tree.end("One is an instance method and one is a class method; no resolution required"))
next
end
+ # Special case: if we remove the namespaces, is everything either an
+ # include or an extend? If so, do nothing - this is fine
+ if children \
+ .reject { |c| c.is_a?(RbiGenerator::Namespace) }
+ .then do |x|
+ !x.empty? && x.all? do |c|
+ c.is_a?(RbiGenerator::Include) || c.is_a?(RbiGenerator::Extend)
+ end
+ end
+
+ Debugging.debug_puts(self, Debugging::Tree.end("Includes/extends do not conflict with namespaces; no resolution required"))
+ next
+ end
+
# Special case: do we have two attributes, one of which is a class
# attribute and the other isn't? If so, do nothing - this is fine
if children.length == 2 &&
children.all? { |c| c.is_a?(RbiGenerator::Attribute) } &&
children.count { |c| T.cast(c, RbiGenerator::Attribute).class_attribute } == 1
@@ -115,18 +138,29 @@
# The types aren't the same, so ask the resolver what to do, and
# insert that (if not nil)
choice = resolver.call("Non-namespace item in a differing namespace conflict is not a single method", non_namespaces)
non_namespaces = []
non_namespaces << choice if choice
- end
+ end
end
non_namespaces.each do |x|
namespace.children << x
end
- first, *rest = namespaces
+ # For certain namespace types the order matters. For example, if there's
+ # both a `Namespace` and `ModuleNamespace` then merging the two would
+ # produce different results depending on which is first.
+ first_index = (
+ namespaces.find_index { |x| RbiGenerator::EnumClassNamespace === x || RbiGenerator::StructClassNamespace === x } ||
+ namespaces.find_index { |x| RbiGenerator::ClassNamespace === x } ||
+ namespaces.find_index { |x| RbiGenerator::ModuleNamespace === x } ||
+ 0
+ )
+
+ first = namespaces.delete_at(first_index)
+ rest = namespaces
else
raise 'unknown merge strategy; this is a Parlour bug'
end
# Can the children merge themselves automatically? If so, let them
@@ -159,20 +193,20 @@
private
sig { params(arr: T::Array[T.untyped]).returns(T.nilable(Symbol)) }
# Given an array, if all elements in the array are instances of the exact
- # same class or are otherwise mergeable (for example Namespace and
+ # same class or are otherwise mergeable (for example Namespace and
# ClassNamespace), returns the kind of merge which needs to be made. A
# return value of nil indicates that the values cannot be merged.
#
# The following kinds are available:
# - They are all the same. (:normal)
- # - There are exactly two types, one of which is Namespace and other is a
+ # - There are exactly two types, one of which is Namespace and other is a
# subclass of it. (:differing_namespaces)
# - One of them is Namespace or a subclass (or both, as described above),
- # and the only other is Method. (also :differing_namespaces)
+ # and the only other is Method. (also :differing_namespaces)
#
# @param arr [Array] The array.
# @return [Symbol] The merge strategy to use, or nil if they can't be
# merged.
def merge_strategy(arr)
@@ -180,24 +214,16 @@
array_types = arr.map { |c| c.class }.uniq
return :normal if array_types.length == 1
# Find all the namespaces and non-namespaces
namespace_types, non_namespace_types = array_types.partition { |x| x <= RbiGenerator::Namespace }
+ exactly_namespace, namespace_subclasses = namespace_types.partition { |x| x == RbiGenerator::Namespace }
- # If there are two namespace types, one should be Namespace and the other
- # should be a subclass of it
- if namespace_types.length == 2
- exactly_namespace, exactly_one_subclass = namespace_types.partition { |x| x == RbiGenerator::Namespace }
+ return nil unless namespace_subclasses.empty? \
+ || (namespace_subclasses.length == 1 && namespace_subclasses.first < RbiGenerator::Namespace) \
+ || namespace_subclasses.to_set == Set[RbiGenerator::ClassNamespace, RbiGenerator::StructClassNamespace] \
+ || namespace_subclasses.to_set == Set[RbiGenerator::ClassNamespace, RbiGenerator::EnumClassNamespace]
- return nil unless exactly_namespace.length == 1 \
- && exactly_one_subclass.length == 1 \
- && exactly_one_subclass.first < RbiGenerator::Namespace
- elsif namespace_types.length != 1
- # The only other valid number of namespaces is 1, where we don't need to
- # check anything
- return nil
- end
-
# It's OK, albeit cursed, for there to be a method with the same name as
# a namespace (Rainbow does this)
return nil if non_namespace_types.length != 0 && non_namespace_types != [RbiGenerator::Method]
:differing_namespaces