Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/rdoc/code_object/any_method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def call_seq
def call_seq=(call_seq)
return if call_seq.nil? || call_seq.empty?

invalidate_name_indexes unless @name
@call_seq = call_seq
end

Expand Down
5 changes: 2 additions & 3 deletions lib/rdoc/code_object/class_module.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def self.from_module(class_type, mod)
klass.section = mod.section

klass.attributes.concat mod.attributes
klass.method_list.concat mod.method_list
klass.replace_methods mod.method_list
klass.aliases.concat mod.aliases
klass.external_aliases.concat mod.external_aliases
klass.constants.concat mod.constants
Expand Down Expand Up @@ -541,8 +541,7 @@ def merge(class_module)
if add
add_method meth
else
@method_list.delete meth
@methods_hash.delete meth.pretty_name
remove_method meth
end
end

Expand Down
46 changes: 32 additions & 14 deletions lib/rdoc/code_object/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class Context < CodeObject

include Comparable

require_relative 'context/method_list'

##
# Types of methods

Expand Down Expand Up @@ -61,9 +63,12 @@ class Context < CodeObject
attr_reader :extends

##
# Methods defined in this context

attr_reader :method_list
# A frozen snapshot of the methods defined in this context.
# Use #add_method, #remove_method or #replace_methods to change the list.
#: () -> Array[AnyMethod]
def method_list
@method_list.to_a.freeze
end

##
# Name of this class excluding namespace. See also full_name
Expand Down Expand Up @@ -132,7 +137,7 @@ def initialize
# Sets the defaults for methods and so-forth

def initialize_methods_etc
@method_list = []
@method_list = MethodList.new
@attributes = []
@aliases = []
@requires = []
Expand Down Expand Up @@ -766,7 +771,7 @@ def find_attribute_named(name)
# Finds a class method with +name+ in this context

def find_class_method_named(name)
@method_list.find { |meth| meth.singleton && meth.name == name }
@method_list.find_named(name) { |meth| meth.singleton }
end

##
Expand Down Expand Up @@ -813,7 +818,7 @@ def find_external_alias_named(name)
# Finds an instance method with +name+ in this context

def find_instance_method_named(name)
@method_list.find { |meth| !meth.singleton && meth.name == name }
@method_list.find_named(name) { |meth| !meth.singleton }
end

##
Expand All @@ -833,13 +838,7 @@ def find_local_symbol(symbol)
# Finds a method named +name+ with singleton value +singleton+.

def find_method(name, singleton)
@method_list.find { |m|
if m.singleton
m.name == name && m.singleton == singleton
else
m.name == name && !m.singleton && !singleton
end
}
@method_list.find_named(name) { |m| m.singleton ? m.singleton == singleton : !singleton }
end

##
Expand All @@ -852,7 +851,7 @@ def find_method_named(name)
when /\A::/
find_method name[2..-1], true
else
@method_list.find { |meth| meth.name == name }
@method_list.find_named(name)
end
end

Expand Down Expand Up @@ -1048,6 +1047,7 @@ def remove_from_documentation?
def remove_invisible(min_visibility)
return if [:private, :nodoc].include? min_visibility
remove_invisible_in @method_list, min_visibility
@method_list.invalidate_name_index
remove_invisible_in @attributes, min_visibility
remove_invisible_in @constants, min_visibility
end
Expand All @@ -1067,6 +1067,24 @@ def remove_invisible_in(array, min_visibility) # :nodoc:
end
end

##
# Removes +method+ from the documentation list and method registrations.
#: (AnyMethod) -> void
def remove_method(method)
@method_list.delete method
@method_list.invalidate_name_index
@methods_hash.delete method.pretty_name
end

##
# Replaces the documentation method list with +methods+.
# Registrations in #methods_hash are unchanged, including filtered methods.
#: (Array[AnyMethod]) -> void
def replace_methods(methods)
@method_list.replace methods
@method_list.invalidate_name_index
end

##
# Tries to resolve unmatched aliases when a method or attribute has just
# been added.
Expand Down
66 changes: 66 additions & 0 deletions lib/rdoc/code_object/context/method_list.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true
module RDoc
class Context
##
# Internal method storage with a lazy name index. Keeping the index on the
# list lets shallow-copied class/module aliases share its invalidation.
# Context exposes only frozen snapshots and invalidates destructive changes.

class MethodList < Array # :nodoc:
# Finds the first method named +name+ accepted by the optional block.
# Keep candidate buckets private so callers cannot mutate the name index.

#: (String?) ?{ (AnyMethod) -> bool } -> AnyMethod?
def find_named(name)
index_names

# Names are retained by reference. Mutable strings can change without
# name=, so only use the index when every name is already frozen.
# Stop at the first match instead of allocating all matching candidates.
if @mutable_names
return find { |method| method.name == name && (!block_given? || yield(method)) }
end

candidates = @name_index[name]
return unless candidates
block_given? ? candidates.find { |method| yield method } : candidates.first
end

#: () -> void
def index_names
indexed = @name_index && @name_index_token[0]
if !indexed || @indexed_length < length
index = indexed ? @name_index : {}
token = indexed ? @name_index_token : [true]
mutable_names = indexed && @mutable_names

# Parsing aliases interleaves lookups with additions. Index just the
# appended tail rather than repeatedly rebuilding the whole list.
(indexed ? @indexed_length : 0).upto(length - 1) do |i|
method = self[i]
method_name = method.name
if method_name.frozen?
(index[method_name] ||= []) << method
else
mutable_names = true
end
method.track_name_index token unless method.frozen?
end

@name_index_token = token
@name_index = index
@indexed_length = length
@mutable_names = mutable_names
end
end
private :index_names

#: () -> void
def invalidate_name_index
@name_index_token[0] = false if @name_index_token
@name_index = nil
end

end
end
end
27 changes: 26 additions & 1 deletion lib/rdoc/code_object/method_attr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,32 @@ class MethodAttr < CodeObject

##
# Name of this method/attribute.
# The supplied string is retained by reference and may be mutated in place.

attr_accessor :name
attr_reader :name

#: (String?) -> String?
def name=(name)
invalidate_name_indexes
@name = name
end

# Track only validity tokens, not the indexes or lists themselves. A method
# can appear in more than one list after module promotion or bulk replacement,
# and must invalidate all of their name indexes when renamed.

#: (Array[bool]) -> void
def track_name_index(token) # :nodoc:
tokens = (@name_index_tokens ||= [])
tokens.reject! { |existing| !existing[0] }
tokens << token unless tokens.any? { |existing| existing.equal? token }
end

#: () -> void
def invalidate_name_indexes # :nodoc:
@name_index_tokens&.each { |token| token[0] = false }
@name_index_tokens = nil
end

##
# public, protected, private
Expand Down Expand Up @@ -95,6 +119,7 @@ def initialize(name, singleton: false)

def initialize_copy(other) # :nodoc:
@full_name = nil
@name_index_tokens = nil
end

def initialize_visibility # :nodoc:
Expand Down
6 changes: 3 additions & 3 deletions lib/rdoc/parser/c.rb
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ def do_aliases
# method that reference the same function.

def add_alias(var_name, class_obj, old_name, new_name, comment, singleton:)
al = Alias.new old_name, new_name, comment, singleton: singleton
al = Alias.new(-old_name, -new_name, comment, singleton: singleton)
al.record_location @top_level
class_obj.add_alias al
@stats.add_alias al
Expand Down Expand Up @@ -850,7 +850,7 @@ def handle_attr(var_name, attr_name, read, write)

name = attr_name.gsub(/rb_intern(?:_const)?\("([^"]+)"\)/, '\1')

attr = Attr.new name, rw, comment
attr = Attr.new(-name, rw, comment)

attr.record_location @top_level
class_obj.add_attribute attr
Expand Down Expand Up @@ -1008,7 +1008,7 @@ def handle_method(type, var_name, meth_name, function, param_count,
type = 'method' # force public
end

meth_obj = AnyMethod.new meth_name, singleton: singleton
meth_obj = AnyMethod.new(-meth_name, singleton: singleton)
meth_obj.c_function = function

p_count = Integer(param_count) rescue -1
Expand Down
8 changes: 4 additions & 4 deletions lib/rdoc/parser/rbs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def merge_attribute_methods(context, name, rw, singleton, comment, type_signatur
end

def rdoc_method_name(decl)
rbs_constructor_decl?(decl) ? 'new' : decl.name.to_s
rbs_constructor_decl?(decl) ? 'new' : decl.name.name
end

def rdoc_method_singleton?(decl)
Expand Down Expand Up @@ -135,7 +135,7 @@ def parse_attr_decl(decl, context)

comment = rdoc_comment_for decl
type_signature_lines = [decl.type.to_s]
name = decl.name.to_s
name = decl.name.name
singleton = decl.kind == :singleton
if attribute = context.find_attribute(name, singleton)
merge_documentation attribute, comment, type_signature_lines if
Expand Down Expand Up @@ -231,8 +231,8 @@ def parse_member_decl(decl, context)

def parse_method_alias_decl(decl, context)
alias_def = Alias.new(
decl.old_name.to_s,
decl.new_name.to_s,
decl.old_name.name,
decl.new_name.name,
rdoc_comment_for(decl),
singleton: decl.kind == :singleton
)
Expand Down
13 changes: 8 additions & 5 deletions lib/rdoc/parser/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ def parse_comment_tomdoc(container, comment, line_no, start_line)

name, = signature.split %r%[ \(]%, 2

meth = AnyMethod.new name
meth = AnyMethod.new(-name)
record_location(meth)
meth.line = start_line
meth.call_seq = signature
Expand Down Expand Up @@ -437,7 +437,7 @@ def handle_meta_method_comment(comment, directives, node)

if attributes
attributes.each do |attr|
a = Attr.new(attr, rw, comment, singleton: @singleton)
a = Attr.new(-attr, rw, comment, singleton: @singleton)
a.store = @store
a.line = line_no
a.visibility = visibility
Expand Down Expand Up @@ -624,7 +624,7 @@ def add_alias_method(old_name, new_name, line_no)
handle_code_object_directives(@container, directives) if directives
return if document_suppressed?

a = Alias.new(old_name, new_name, comment, singleton: @singleton)
a = Alias.new(-old_name, -new_name, comment, singleton: @singleton)
handle_modifier_directive(a, line_no)
a.store = @store
a.line = line_no
Expand All @@ -645,7 +645,7 @@ def add_attributes(names, rw, line_no)
return unless @container.document_children

names.each do |symbol|
a = Attr.new(symbol.to_s, rw, comment, singleton: @singleton)
a = Attr.new(-symbol.to_s, rw, comment, singleton: @singleton)
a.store = @store
a.line = line_no
a.type_signature_lines = type_signature_lines
Expand Down Expand Up @@ -753,6 +753,9 @@ def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility:
end
end

# Keep parser-owned names immutable and deduplicated without freezing
# strings supplied to the code-object API.
meth.name = -meth.name
record_location(meth)
container.add_method(meth)
token_stream_loader = @colorizer_context.token_stream_loader(node_id) if node_id
Expand Down Expand Up @@ -1164,7 +1167,7 @@ def visit_def_node(node)
# `def (unknown expression).method_name` is not documentable
return
end
name = node.name.to_s
name = node.name.name
params, block_params, calls_super = MethodSignatureVisitor.scan_signature(node)
@scanner.add_method(
name,
Expand Down
4 changes: 2 additions & 2 deletions lib/rdoc/store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def clear_file_contributions(relative_name, keep_position: false)

top_level.classes_or_modules.each do |cm|
# Remove methods and attributes contributed by this file
cm.method_list.reject! { |m| m.file == top_level }
cm.replace_methods cm.method_list.reject { |m| m.file == top_level }
cm.attributes.reject! { |a| a.file == top_level }

# Rebuild methods_hash from remaining methods and attributes
Expand Down Expand Up @@ -706,7 +706,7 @@ def load_all
load_method module_name, method.full_name
end

mod.method_list.replace loaded_methods
mod.replace_methods loaded_methods

loaded_attributes = mod.attributes.map do |attribute|
load_method module_name, attribute.full_name
Expand Down
Loading
Loading