module T::Private::Methods
def self._check_final_ancestors(target, target_ancestors, source_method_names, source)
we assume that source_method_names has already been filtered to only include method
is already a method defined on one of target_ancestors with the same name that is final.
the final instance methods of target and source_method_names. so, for every m in source_method_names, check if there
when target includes a module with instance methods source_method_names, ensure there is zero intersection between
def self._check_final_ancestors(target, target_ancestors, source_method_names, source) source_ancestors = nil # use reverse_each to check farther-up ancestors first, for better error messages. target_ancestors.reverse_each do |ancestor| final_methods = @modules_with_final.fetch(ancestor.object_id, nil) # In this case, either ancestor didn't have any final methods anywhere in its # ancestor chain, or ancestor did have final methods somewhere in its ancestor # chain, but no final methods defined in ancestor itself. Either way, there # are no final methods to check here, so we can move on to the next ancestor. next unless final_methods source_method_names.each do |method_name| next unless final_methods.include?(method_name) # If we get here, we are defining a method that some ancestor declared as # final. however, we permit a final method to be defined multiple # times if it is the same final method being defined each time. if source if !source_ancestors source_ancestors = source.ancestors # filter out things without actual final methods just to make sure that # the below checks (which should be uncommon) go as quickly as possible. source_ancestors.select! do |a| @modules_with_final.fetch(a.object_id, nil) end end # final-ness means that there should be no more than one index for which # the below block returns true. defining_ancestor_idx = source_ancestors.index do |a| @modules_with_final.fetch(a.object_id).include?(method_name) end next if defining_ancestor_idx && source_ancestors[defining_ancestor_idx] == ancestor end definition_file, definition_line = T::Private::Methods.signature_for_method(ancestor.instance_method(method_name)).method.source_location is_redefined = target == ancestor caller_loc = caller_locations&.find {|l| !l.to_s.match?(%r{sorbet-runtime[^/]*/lib/})} extra_info = "\n" if caller_loc extra_info = (is_redefined ? "Redefined" : "Overridden") + " here: #{caller_loc.path}:#{caller_loc.lineno}\n" end error_message = "The method `#{method_name}` on #{ancestor} was declared as final and cannot be " + (is_redefined ? "redefined" : "overridden in #{target}") pretty_message = "#{error_message}\n" \ "Made final here: #{definition_file}:#{definition_line}\n" \ "#{extra_info}" begin raise pretty_message rescue => e # sig_validation_error_handler raises by default; on the off chance that # it doesn't raise, we need to ensure that the rest of signature building # sees a consistent state. This sig failed to validate, so we should get # rid of it. If we don't do this, errors of the form "You called sig # twice without declaring a method in between" will non-deterministically # crop up in tests. T::Private::DeclState.current.reset! T::Configuration.sig_validation_error_handler(e, {}) end end end end