Class: Parlour::ConflictResolver
- Inherits:
-
Object
- Object
- Parlour::ConflictResolver
- 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
-
#initialize ⇒ ConflictResolver
constructor
A new instance of ConflictResolver.
-
#resolve_conflicts(namespace) {|message, candidates| ... } ⇒ void
Given a namespace, attempts to automatically resolve conflicts in the namespace’s definitions.
Constructor Details
#initialize ⇒ ConflictResolver
Returns a new instance of ConflictResolver.
10 11 12 |
# File 'lib/parlour/conflict_resolver.rb', line 10 def initialize @debugging_tree = Debugging::Tree.new(colour: true) end |
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.
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 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/parlour/conflict_resolver.rb', line 47 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 # (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")) # 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: 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 deduplicate_mixins_of_name(namespace, name) 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 Debugging.debug_puts(self, @debugging_tree.end("One is an instance attribute and one is a class attribute; no resolution required")) next end # Optimization for 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 # 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 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 |