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