Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change to improved performance AST matching algorithm #1348

Merged
merged 1 commit into from
Jul 31, 2022
Merged
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
8 changes: 7 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# v0.11.14 2022-07-25
# v0.11.14 2022-07-31

* [#1348](https://github.com/mbj/mutant/pull/1348)

Change to improved AST matching performance. Mutant boot performance
is positively affected mostly on larger projects with larger AST node
count per file.

* [#1347](https://github.com/mbj/mutant/pull/1347/files)

Expand Down
60 changes: 31 additions & 29 deletions lib/mutant/ast.rb
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
# frozen_string_literal: true

module Mutant
# AST helpers
module AST

# Find last node satisfying predicate (as block)
#
# @return [Parser::AST::Node]
# if satisfying node is found
#
# @yield [Parser::AST::Node]
#
# @yieldreturn [Boolean]
# true in case node satisfies predicate
#
# @return [nil]
# otherwise
def self.find_last_path(node, &predicate)
fail ArgumentError, 'block expected' unless block_given?
path = []
walk(node, [node]) do |candidate, stack|
if predicate.call(candidate)
path = stack.dup
end
class AST
include Adamantium, Anima.new(
:node,
:comment_associations
)

class View
include Adamantium, Anima.new(:node, :path)
end

def view(symbol)
type_map.fetch(symbol, EMPTY_HASH).map do |node, path|
View.new(node: node, path: path)
end
end

private

def type_map
type_map = {}

walk_path(node) do |node, path|
path_map = type_map[node.type] ||= {}.tap(&:compare_by_identity)
path_map[node] = path
end
path

type_map
end
memoize :type_map

def self.walk(node, stack, &block)
block.call(node, stack)
def walk_path(node, stack = [node.type], &block)
block.call(node, stack.dup)
node.children.grep(::Parser::AST::Node) do |child|
stack.push(child)
walk(child, stack, &block)
stack.push(child.type)
walk_path(child, stack, &block)
stack.pop
end
end
private_class_method :walk

end # AST
end # Mutant
6 changes: 3 additions & 3 deletions lib/mutant/ast/find_metaclass_containing.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# Given an AST, finds the sclass that directly(-ish) contains the provided
# node.
# This won't match arbitrarily complex structures - it only searches the
Expand All @@ -10,7 +10,7 @@ module AST
# Descending into 'begin' nodes is supported because these are generated for
# the one-line syntax class << self; def foo; end
class FindMetaclassContaining
include NodePredicates, Concord.new(:root, :target), Procto
include NodePredicates, Concord.new(:ast, :target), Procto

SCLASS_BODY_INDEX = 1

Expand All @@ -22,7 +22,7 @@ class FindMetaclassContaining
#
# @api private
def call
Structure.for(root.type).each_node(root) do |current|
Structure.for(ast.node.type).each_node(ast.node) do |current|
return current if n_sclass?(current) && metaclass_of?(current)
end

Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/meta.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# Node meta information mixin
module Meta
end # Meta
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/meta/const.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# Node meta information mixin
module Meta

Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/meta/optarg.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# Node meta information mixin
module Meta

Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/meta/resbody.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# Node meta information mixin
module Meta

Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/meta/send.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# Node meta information mixin
module Meta

Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/meta/symbol.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# Node meta information mixin
module Meta

Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/named_children.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST

# Helper methods to define named children
module NamedChildren
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/node_predicates.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# Module for node predicates
module NodePredicates

Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/nodes.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# Singleton nodes
module Nodes
extend Sexp
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/pattern.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
class Pattern
include Adamantium

Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/pattern/lexer.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
class Pattern
# rubocop:disable Metrics/ClassLength
class Lexer
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/pattern/parser.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# rubocop:disable Metrics/ClassLength
# rubocop:disable Metrics/MethodLength
class Pattern
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/pattern/source.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
class Pattern
class Source
include Anima.new(:string)
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/pattern/token.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
class Pattern
class Token
include Anima.new(:type, :value, :location)
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/regexp.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# Regexp source mapper
module Regexp
# Parse regex string into expression
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/regexp/transformer.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
module Regexp
# Regexp bijective mapper
#
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/regexp/transformer/direct.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
module Regexp
class Transformer
# Transformer for nodes which map directly to other domain
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/regexp/transformer/named_group.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
module Regexp
class Transformer
# Transformer for named groups
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/regexp/transformer/options_group.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
module Regexp
class Transformer
# Transformer for option groups
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/regexp/transformer/quantifier.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
module Regexp
class Transformer
# Transformer for regexp quantifiers
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/regexp/transformer/recursive.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
module Regexp
class Transformer
# Transformer for nodes with children
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/regexp/transformer/root.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
module Regexp
class Transformer
# Transformer for root nodes
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/regexp/transformer/text.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
module Regexp
class Transformer
# Regexp AST transformer for nodes that encode a text value
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/sexp.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# Mixin for node sexp syntax
module Sexp

Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/structure.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# AST Structure metadata
# rubocop:disable Metrics/ModuleLength
module Structure
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/ast/types.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Mutant
module AST
class AST
# Groups of node types
module Types # rubocop:disable Metrics/ModuleLength
ASSIGNABLE_VARIABLES = Set.new(%i[ivasgn lvasgn cvasgn gvasgn]).freeze
Expand Down
17 changes: 9 additions & 8 deletions lib/mutant/matcher/method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,9 @@ def call
def skip?
location = source_location

file = location&.first

if location.nil? || !file.end_with?('.rb')
if location.nil? || !location.first.end_with?('.rb')
env.warn(SOURCE_LOCATION_WARNING_FORMAT % target_method)
elsif matched_node_path.any?(&method(:n_block?))
elsif matched_view&.path&.any?(&:block.public_method(:equal?))
env.warn(CLOSURE_WARNING_FORMAT % target_method)
end
end
Expand Down Expand Up @@ -96,7 +94,7 @@ def sorbet_signature
end

def subject
node = matched_node_path.last || return
node = matched_view&.node || return

self.class::SUBJECT_CLASS.new(
config: subject_config(node),
Expand All @@ -113,10 +111,13 @@ def subject_config(node)
)
end

def matched_node_path
AST.find_last_path(ast.node, &method(:match?))
def matched_view
ast
.view(self.class::MATCH_NODE_TYPE)
.select { |view| match?(view.node) }
.last
end
memoize :matched_node_path
memoize :matched_view

def visibility
# This can be cleaned up once we are on >ruby-3.0
Expand Down
5 changes: 3 additions & 2 deletions lib/mutant/matcher/method/instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ def self.memoized_method?(scope, method_name)

# Instance method specific evaluator
class Evaluator < Evaluator
SUBJECT_CLASS = Subject::Method::Instance
NAME_INDEX = 0
MATCH_NODE_TYPE = :def
NAME_INDEX = 0
SUBJECT_CLASS = Subject::Method::Instance

private

Expand Down
Loading