Module: Solargraph::Parser::ParserGem::NodeMethods::DeepInference

Defined in:
lib/solargraph/parser/parser_gem/node_methods.rb

Overview

Concepts:

- statement - one single node in the AST.  Generally used
  synonymously with how the Parser gem uses the term
  'expression'.  This may have side effects (e.g.,
  registering a method in the namespace, modifying
  variables or doing I/O).  It may encapsulate multiple
  other statements (see compound statement).

- value - something that can be assigned to a variable by
  evaluating a statement

- value node - the 'lowest level' AST node whose return
  type is a subset of the value type of the overall
  statement.  Might be a literal, a method call, etc - the
  goal is to find the lowest level node, which we can use
  Chains and Pins later on to determine the type of.

  e.g., if the node 'b ? 123 : 456' were a return value, we
  know the actual return values possible are 123 and 456,
  and can disregard the rest.

- value type - the type representing the multiple possible
  values that can result from evaluation of the statement.

- return type - the type describing the values a statement
  might evaluate to.  When used with a method, the term
  describes the values that may result from the method
  being called, and includes explicit return statements
  within the method body's closure.

- method body - a compound statement with parameters whose
  return value type must account both for the explicit
  'return' statemnts as well as the final statements
  executed in any given control flow through the method.

- explicit return statement - a statement which, when part of a
   method body, is a possible value of a call to that method -
   e.g., "return 123"

- compound statement - a statement which can be expanded to
   be multiple statements in a row, executed in the context
   of a method which can be explicitly returned from.

- value position - the positions in the AST where the
  return type of the statement would be one of the return
  types of any compound statements it is a part of.  For a
  compound statement, the last of the child statements
  would be in return position.  This concept can be applied
  recursively through e.g. conditionals to find a list of
  statements in value positions.

Constant Summary collapse

CONDITIONAL_ALL_BUT_FIRST =
%i[if unless].freeze
ONLY_ONE_CHILD =
[:return].freeze
FIRST_TWO_CHILDREN =
[:rescue].freeze
COMPOUND_STATEMENTS =
%i[begin kwbegin].freeze
SKIPPABLE =
%i[def defs class sclass module].freeze
FUNCTION_VALUE =
[:block].freeze
CASE_STATEMENT =
[:case].freeze

Class Method Summary collapse

Class Method Details

.from_method_body(node) ⇒ Array<AST::Node>

Low-level value nodes from both nodes in value position as well as explicit return statements in the method’s closure.

Parameters:

  • node (AST::Node)

    a method body compound statement

Returns:

  • (Array<AST::Node>)

    low-level value nodes from both nodes in value position as well as explicit return statements in the method’s closure.



412
413
414
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 412

def from_method_body node
  from_value_position_statement(node, include_explicit_returns: true)
end

.from_value_position_compound_statement(parent) ⇒ Array<Parser::AST::Node>

Treat parent as as a begin block and use the last node’s return node plus any explicit return nodes’ return nodes. e.g.,

  123
  456
  return 'a' if foo == bar
  789

would return 'a' and 789.

Parameters:

  • parent (Parser::AST::Node)

Returns:

  • (Array<Parser::AST::Node>)


491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 491

def from_value_position_compound_statement parent
  result = []
  nodes = parent.children.select { |n| n.is_a?(AST::Node) }
  nodes.each_with_index do |node, idx|
    if node.type == :block
      result.concat explicit_return_values_from_compound_statement(node.children[2])
    elsif node.type == :rescue
      # body statements
      result.concat from_value_position_statement(node.children[0])
      # rescue statements
      result.concat from_value_position_statement(node.children[1])
    elsif SKIPPABLE.include?(node.type)
      next
    elsif node.type == :resbody
      result.concat reduce_to_value_nodes([node.children[2]])
    elsif node.type == :return
      result.concat reduce_to_value_nodes([node.children[0]])
      # Return here because the rest of the code is
      # unreachable and shouldn't be looked at
      return result
    else
      result.concat explicit_return_values_from_compound_statement(node)
    end
    # handle last line of compound statements, which is in
    # value position.  we already have the explicit values
    # from above; now we need to also gather the value
    # position nodes
    if idx == nodes.length - 1
      result.concat from_value_position_statement(nodes.last,
                                                  include_explicit_returns: false)
    end
  end
  result
end

.from_value_position_statement(node, include_explicit_returns: true) ⇒ Array<Parser::AST::Node>

Look at known control statements and use them to find more specific return nodes.

Parameters:

  • node (AST::Node)

    Statement which is in value position for a method body

  • include_explicit_returns (Boolean) (defaults to: true)

    If true, include the value nodes of the parameter of the ‘return’ statements in the type returned.

Returns:

  • (Array<Parser::AST::Node>)


434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 434

def from_value_position_statement node, include_explicit_returns: true
  # STDERR.puts("from_expression called on #{node.inspect}")
  return [] unless node.is_a?(::Parser::AST::Node)
  # @type [Array<Parser::AST::Node>]
  result = []
  if COMPOUND_STATEMENTS.include?(node.type)
    result.concat from_value_position_compound_statement node
  elsif CONDITIONAL_ALL_BUT_FIRST.include?(node.type)
    # @sg-ignore Need to add nil check here
    result.concat reduce_to_value_nodes(node.children[1..])
    # result.push NIL_NODE unless node.children[2]
  elsif ONLY_ONE_CHILD.include?(node.type)
    result.concat reduce_to_value_nodes([node.children[0]])
  elsif FIRST_TWO_CHILDREN.include?(node.type)
    result.concat reduce_to_value_nodes([node.children[0], node.children[1]])
  elsif FUNCTION_VALUE.include?(node.type)
    # the block itself is a first class value that could be returned
    result.push node
    # @todo any explicit returns actually return from
    #   scope in which the proc is run.  This asssumes
    #   that the function is executed here.
    if include_explicit_returns
      result.concat explicit_return_values_from_compound_statement(node.children[2])
    end
  elsif CASE_STATEMENT.include?(node.type)
    # @sg-ignore Need to add nil check here
    node.children[1..].each do |cc|
      if cc.nil?
        result.push NIL_NODE
      elsif cc.type == :when
        result.concat reduce_to_value_nodes([cc.children.last])
      else
        # else clause in case
        result.concat reduce_to_value_nodes([cc])
      end
    end
  elsif node.type == :resbody
    result.concat reduce_to_value_nodes([node.children[2]])
  else
    result.push node
  end
  result
end

.value_position_nodes_only(node) ⇒ Array<AST::Node>

Returns low-level value nodes in value position. Does not include explicit return statements.

Parameters:

  • node (AST::Node)

    an individual statement, to be evaluated outside the context of a containing method

Returns:

  • (Array<AST::Node>)

    low-level value nodes in value position. Does not include explicit return statements



421
422
423
# File 'lib/solargraph/parser/parser_gem/node_methods.rb', line 421

def value_position_nodes_only node
  from_value_position_statement(node, include_explicit_returns: false)
end