Class: Audition::Static::LiteralClassifier

Inherits:
Object
  • Object
show all
Defined in:
lib/audition/static/literal_classifier.rb

Overview

Classifies a Prism expression node by Ractor shareability:

:shareable         proven deeply shareable
:mutable_string    unfrozen String literal
:mutable_container Array/Hash/Set literal or constructor
:mutable_call      unfrozen String, Regexp, or sentinel
                 Object returned by a call (`.tr`,
                 `format`, `Regexp.new`, `Object.new`)
:shallow_freeze    frozen container with mutable elements
:sync_primitive    Mutex/Queue/... constructor
:proc              lambda or proc
:default_proc      Hash.new with a block; the block
                 survives .freeze and stays unshareable
:unknown           cannot tell statically

Constant Summary collapse

SYNC_PRIMITIVES =
%w[
  Mutex Monitor Queue SizedQueue ConditionVariable
  Thread::Mutex Thread::Queue Thread::SizedQueue
  Thread::ConditionVariable
].freeze
SHAREABLE_FACTORIES =
%w[Struct Class Module].freeze
SENTINEL_FACTORIES =

Sentinels: NOT_GIVEN = Object.new raises when read from a worker until frozen, and a frozen bare Object is shareable (verified on Ruby 4.0.6). BasicObject has no #freeze.

%w[Object BasicObject].freeze
UNFREEZABLE_COLLECTIONS =

Concurrent::Map defines no #freeze, so make_shareable raises NoMethodError on it; a constant holding one can never be shared (verified on 4.0.6 with concurrent-ruby).

%w[Concurrent::Map].freeze
STRING_ONLY_METHODS =

Calls returning a fresh, unfrozen String or Regexp; # frozen_string_literal: true covers literals only. Both shapes (.tr and Regexp.new) turn up in constants in the wild. These names belong to String alone in core, so any receiver qualifies.

%i[
  tr tr_s gsub sub squeeze strip lstrip rstrip chomp chop
  center ljust rjust encode scrub unicode_normalize
].freeze
STRING_LITERAL_METHODS =

Unambiguous only on a String literal receiver: Symbols and numbers define these too and return shareable values.

%i[
  + * % upcase downcase capitalize swapcase reverse dup
  succ next
].freeze
FORMATTERS =
%i[format sprintf].freeze
REGEXP_FACTORIES =
%i[new union compile].freeze

Instance Method Summary collapse

Constructor Details

#initialize(frozen_string_literal:) ⇒ LiteralClassifier

Returns a new instance of LiteralClassifier.

Parameters:

  • frozen_string_literal (Boolean)

    whether the file has the frozen_string_literal magic comment



57
58
59
# File 'lib/audition/static/literal_classifier.rb', line 57

def initialize(frozen_string_literal:)
  @frozen_string_literal = frozen_string_literal
end

Instance Method Details

#classify(node) ⇒ Symbol

Returns classification, see class docs.

Parameters:

  • node (Prism::Node)

    an expression node

Returns:

  • (Symbol)

    classification, see class docs



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/audition/static/literal_classifier.rb', line 63

def classify(node)
  case node
  when Prism::IntegerNode, Prism::FloatNode,
       Prism::RationalNode, Prism::ImaginaryNode,
       Prism::SymbolNode, Prism::InterpolatedSymbolNode,
       Prism::TrueNode, Prism::FalseNode, Prism::NilNode,
       Prism::RegularExpressionNode,
       Prism::InterpolatedRegularExpressionNode
    :shareable
  when Prism::StringNode
    @frozen_string_literal ? :shareable : :mutable_string
  when Prism::InterpolatedStringNode
    classify_interpolated_string(node)
  when Prism::ArrayNode, Prism::HashNode,
       Prism::KeywordHashNode
    container_kind(node)
  when Prism::RangeNode
    ends = [node.left, node.right].compact
    if ends.all? { |n| classify(n) == :shareable }
      :shareable
    else
      :unknown
    end
  when Prism::LambdaNode
    :proc
  when Prism::CallNode
    classify_call(node)
  when Prism::IfNode
    ternary_kind(node)
  else
    :unknown
  end
end

#const_name(node) ⇒ Object

::Mutex and Mutex are the same constant for matching purposes; the leading colons are stripped.



99
100
101
102
103
104
105
106
# File 'lib/audition/static/literal_classifier.rb', line 99

def const_name(node)
  case node
  when Prism::ConstantReadNode
    node.name.to_s
  when Prism::ConstantPathNode
    node.location.slice.delete_prefix("::")
  end
end

#frozen_kind(value) ⇒ Object

What value.freeze would classify as, for the check to choose a plain .freeze over a deep wrap: :shareable when every element is provably shareable, :shallow_freeze when one is provably mutable, :unknown otherwise.



112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/audition/static/literal_classifier.rb', line 112

def frozen_kind(value)
  case value
  when Prism::ArrayNode, Prism::HashNode,
       Prism::KeywordHashNode
    deep_classify(value.elements)
  when Prism::CallNode
    elements = set_elements(value)
    elements ? deep_classify(elements) : :unknown
  else
    :unknown
  end
end