Class: Ryac::Pipeline::ConstantFold

Inherits:
Stage
  • Object
show all
Defined in:
lib/ryac/pipeline/constant_fold.rb,
sig/ryac/pipeline/constant_fold.rbs

Constant Summary collapse

FOLDABLE_OPS =

Returns:

  • (Array[Symbol])
%i[+ - * / % ** << >> & | ^].freeze
INTEGER_ONLY_OPS =

Returns:

  • (Array[Symbol])
%i[<< >> & | ^].freeze

Instance Method Summary collapse

Methods inherited from Stage

#analysis, #analysis_options, #call, #finish, #fixpoint?, #needs_analysis?, new

Methods included from SourcePatcher

#apply_patches, #mk, #src, #verify_parses

Instance Method Details

#apply_op(lhs, op, rhs) ⇒ Numeric?

The operands are Integer | Float at runtime, but RBS's Numeric does not carry the arithmetic operators and the op is dispatched on a Symbol tag, so the operands stay untyped.

Parameters:

  • lhs (Object)
  • op (Symbol)
  • rhs (Object)

Returns:

  • (Numeric, nil)


17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'sig/ryac/pipeline/constant_fold.rbs', line 17

def apply_op(lhs, op, rhs)
  case op
  when :+ then lhs + rhs
  when :- then lhs - rhs
  when :* then lhs * rhs
  when :/ then lhs / rhs
  when :% then lhs % rhs
  when :** then lhs ** rhs
  when :<< then lhs << rhs
  when :>> then lhs >> rhs
  when :& then lhs & rhs
  when :| then lhs | rhs
  when :^ then lhs ^ rhs
  end
end

#collect(ctx, patches) ⇒ void

This method returns an undefined value.

Parameters:



9
10
11
# File 'lib/ryac/pipeline/constant_fold.rb', line 9

def collect(ctx, patches)
  walk(ctx.ast, patches)
end

#try_constant_fold(node) ⇒ Numeric?

Parameters:

  • node (Prism::Node)

Returns:

  • (Numeric, nil)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/ryac/pipeline/constant_fold.rb', line 29

def try_constant_fold(node)
  case node
  when Prism::IntegerNode then node.value
  when Prism::FloatNode then node.value
  when Prism::ParenthesesNode
    body = node.body
    if body.is_a?(Prism::StatementsNode) && body.body.size == 1
      try_constant_fold(body.body.first)
    end
  when Prism::CallNode
    if node.receiver && node.arguments.nil? && node.name == :-@
      inner = try_constant_fold(node.receiver)
      return nil unless inner.is_a?(Numeric)
      -inner
    else
      try_fold_binary_op(node)
    end
  end
end

#try_fold_binary_op(node) ⇒ Numeric?

Parameters:

  • node (Prism::CallNode)

Returns:

  • (Numeric, nil)


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
# File 'lib/ryac/pipeline/constant_fold.rb', line 49

def try_fold_binary_op(node)
  return nil unless node.receiver
  return nil unless node.arguments&.arguments&.size == 1
  return nil if node.block

  op = node.name
  return nil unless FOLDABLE_OPS.include?(op)

  lhs = try_constant_fold(node.receiver)
  return nil unless lhs.is_a?(Numeric)

  # arguments proved non-nil above
  rhs = try_constant_fold(node.arguments.arguments.first) # steep:ignore NoMethod
  return nil unless rhs.is_a?(Numeric)

  return nil if INTEGER_ONLY_OPS.include?(op) && !(lhs.is_a?(Integer) && rhs.is_a?(Integer))
  return nil if (op == :/ || op == :%) && rhs == 0

  result = apply_op(lhs, op, rhs)
  return nil unless result.is_a?(Integer) || result.is_a?(Float)
  return nil if result.is_a?(Float) && (result.nan? || result.infinite?)

  result
rescue StandardError
  nil
end

#walk(node, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::Node)
  • patches (Array[patch_entry])


15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/ryac/pipeline/constant_fold.rb', line 15

def walk(node, patches)
  if node.is_a?(Prism::CallNode)
    folded = try_constant_fold(node)
    if folded
      replacement = folded.to_s
      if replacement.bytesize < node.location.length
        patches << mk(node, replacement)
        return
      end
    end
  end
  node.compact_child_nodes.each { |child| walk(child, patches) }
end