Class: Parlour::ConflictResolver

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/parlour/conflict_resolver.rb

Overview

Responsible for resolving conflicts (that is, multiple definitions with the same name) between objects defined in the same namespace.

Instance Method Summary collapse

Instance Method Details

#resolve_conflicts(namespace) {|message, candidates| ... } ⇒ void

This method returns an undefined value.

Given a namespace, attempts to automatically resolve conflicts in the namespace’s definitions. (A conflict occurs when multiple objects share the same name.)

All children of the given namespace which are also namespaces are processed recursively, so passing RbiGenerator#root will eliminate all conflicts in the entire object tree.

If automatic resolution is not possible, the block passed to this method is invoked and passed two arguments: a message on what the conflict is, and an array of candidate objects. The block should return one of these candidate objects, which will be kept, and all other definitions are deleted. Alternatively, the block may return nil, which will delete all definitions. The block may be invoked many times from one call to #resolve_conflicts, one for each unresolvable conflict.

Parameters:

Yield Parameters:

  • message (String)

    A descriptional message on what the conflict is.

  • candidates (Array<RbiGenerator::RbiObject>)

    The objects for which there is a conflict.

Yield Returns:



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/parlour/conflict_resolver.rb', line 41

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)

  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"))

      # Special case: do we have two methods, one of which is a class method 
      # and the other isn't? If so, do nothing - this is fine
      if children.length == 2 &&
        children.all? { |c| c.is_a?(RbiGenerator::Method) } &&
        children.count { |c| T.cast(c, RbiGenerator::Method).class_method } == 1

        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: 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

        Debugging.debug_puts(self, Debugging::Tree.end("One is an instance attribute and one is a class attribute; no resolution required"))
        next
      end

      # Special case: are they all clearly equal? If so, remove all but one
      if all_eql?(children)
        Debugging.debug_puts(self, Debugging::Tree.end("All children are identical"))

        # All of the children are the same, so this deletes all of them
        namespace.children.delete(T.must(children.first))
      
        # Re-add one child
        namespace.children << T.must(children.first)
        next
      end

      # We found a conflict!
      # Start by removing all the conflicting items
      children.each do |c|
        namespace.children.delete(c)
      end

      # Check that the types of the given objects allow them to be merged,
      # and get the strategy to use
      strategy = merge_strategy(children)
      unless strategy
        Debugging.debug_puts(self, Debugging::Tree.end("Children are unmergeable types; requesting manual resolution"))
        # The types aren't the same, so ask the resolver what to do, and
        # insert that (if not nil)
        choice = resolver.call("Different kinds of definition for the same name", children)
        namespace.children << choice if choice
        next
      end

      case strategy
      when :normal
        first, *rest = children
      when :differing_namespaces
        # Let the namespaces be merged normally, but handle the method here
        namespaces, non_namespaces = children.partition { |x| RbiGenerator::Namespace === x }

        # If there is any non-namespace item in this conflict, it should be
        # a single method
        if non_namespaces.length != 0
          unless non_namespaces.length == 1 && RbiGenerator::Method === non_namespaces.first
            Debugging.debug_puts(self, Debugging::Tree.end("Non-namespace item in a differing namespace conflict is not a single method; requesting manual resolution"))
            # 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

        non_namespaces.each do |x|
          namespace.children << x
        end

        first, *rest = namespaces
      else
        raise 'unknown merge strategy; this is a Parlour bug'
      end

      # Can the children merge themselves automatically? If so, let them
      first, rest = T.must(first), T.must(rest)
      if T.must(first).mergeable?(T.must(rest))
        Debugging.debug_puts(self, Debugging::Tree.end("Children are all mergeable; resolving automatically"))
        first.merge_into_self(rest)
        namespace.children << first
        next
      end

      # I give up! Let it be resolved manually somehow
      Debugging.debug_puts(self, Debugging::Tree.end("Unable to resolve automatically; requesting manual resolution"))
      choice = resolver.call("Can't automatically resolve", children)
      namespace.children << choice if choice
    else
      Debugging.debug_puts(self, Debugging::Tree.end("No conflicts"))
    end
  end

  Debugging.debug_puts(self, Debugging::Tree.here("Resolving children..."))

  # Recurse to child namespaces
  namespace.children.each do |child|
    resolve_conflicts(child, &resolver) if RbiGenerator::Namespace === child
  end

  Debugging.debug_puts(self, Debugging::Tree.end("All children done"))
end