ryac 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +145 -0
  4. data/Rakefile +45 -0
  5. data/Steepfile +9 -0
  6. data/bin/console +11 -0
  7. data/bin/ryac +134 -0
  8. data/bin/setup +8 -0
  9. data/lib/ryac/analysis/constant/collection.rb +332 -0
  10. data/lib/ryac/analysis/constant/rename_mapping.rb +346 -0
  11. data/lib/ryac/analysis/cvar/collection.rb +37 -0
  12. data/lib/ryac/analysis/gvar/collection.rb +36 -0
  13. data/lib/ryac/analysis/gvar/rename_mapping.rb +82 -0
  14. data/lib/ryac/analysis/ivar/collection.rb +281 -0
  15. data/lib/ryac/analysis/keyword/collection.rb +213 -0
  16. data/lib/ryac/analysis/keyword/rename_mapping.rb +167 -0
  17. data/lib/ryac/analysis/local_scopes.rb +582 -0
  18. data/lib/ryac/analysis/method/collection.rb +298 -0
  19. data/lib/ryac/analysis/method/rename_mapping.rb +337 -0
  20. data/lib/ryac/analysis/method_aliases.rb +83 -0
  21. data/lib/ryac/analysis/method_aliasing.rb +65 -0
  22. data/lib/ryac/analysis/nesting.rb +123 -0
  23. data/lib/ryac/analysis/scope_management.rb +25 -0
  24. data/lib/ryac/analysis/site_bucket_mapping.rb +127 -0
  25. data/lib/ryac/analysis/type_oracle.rb +386 -0
  26. data/lib/ryac/ast_utils.rb +321 -0
  27. data/lib/ryac/errors.rb +32 -0
  28. data/lib/ryac/gem_resolver.rb +55 -0
  29. data/lib/ryac/minifier.rb +169 -0
  30. data/lib/ryac/name_generator.rb +68 -0
  31. data/lib/ryac/pipeline/analyzer.rb +264 -0
  32. data/lib/ryac/pipeline/attr_decl_shorten.rb +61 -0
  33. data/lib/ryac/pipeline/boolean_shorten.rb +26 -0
  34. data/lib/ryac/pipeline/char_shorten.rb +20 -0
  35. data/lib/ryac/pipeline/compactor.rb +1202 -0
  36. data/lib/ryac/pipeline/concatenator.rb +248 -0
  37. data/lib/ryac/pipeline/constant_aliaser.rb +240 -0
  38. data/lib/ryac/pipeline/constant_fold.rb +93 -0
  39. data/lib/ryac/pipeline/control_flow_simplify.rb +352 -0
  40. data/lib/ryac/pipeline/data_types.rb +179 -0
  41. data/lib/ryac/pipeline/endless_method.rb +60 -0
  42. data/lib/ryac/pipeline/errors.rb +94 -0
  43. data/lib/ryac/pipeline/file_collector.rb +360 -0
  44. data/lib/ryac/pipeline/method_renamer.rb +151 -0
  45. data/lib/ryac/pipeline/paren_optimizer.rb +161 -0
  46. data/lib/ryac/pipeline/rename_patcher.rb +274 -0
  47. data/lib/ryac/pipeline/source_patcher.rb +54 -0
  48. data/lib/ryac/pipeline/spelling_shorten.rb +60 -0
  49. data/lib/ryac/pipeline/stage.rb +72 -0
  50. data/lib/ryac/pipeline/stage_runner.rb +84 -0
  51. data/lib/ryac/pipeline/variable_renamer.rb +125 -0
  52. data/lib/ryac/union_find.rb +42 -0
  53. data/lib/ryac/version.rb +5 -0
  54. data/lib/ryac.rb +29 -0
  55. data/rbs_collection.lock.yaml +224 -0
  56. data/rbs_collection.yaml +25 -0
  57. data/sig/ryac/analysis/constant/collection.rbs +30 -0
  58. data/sig/ryac/analysis/constant/rename_mapping.rbs +60 -0
  59. data/sig/ryac/analysis/cvar/collection.rbs +11 -0
  60. data/sig/ryac/analysis/gvar/collection.rbs +9 -0
  61. data/sig/ryac/analysis/gvar/rename_mapping.rbs +23 -0
  62. data/sig/ryac/analysis/ivar/collection.rbs +26 -0
  63. data/sig/ryac/analysis/keyword/collection.rbs +27 -0
  64. data/sig/ryac/analysis/keyword/rename_mapping.rbs +31 -0
  65. data/sig/ryac/analysis/local_scopes.rbs +77 -0
  66. data/sig/ryac/analysis/method/collection.rbs +25 -0
  67. data/sig/ryac/analysis/method/rename_mapping.rbs +82 -0
  68. data/sig/ryac/analysis/method_aliases.rbs +10 -0
  69. data/sig/ryac/analysis/method_aliasing.rbs +9 -0
  70. data/sig/ryac/analysis/nesting.rbs +9 -0
  71. data/sig/ryac/analysis/scope_management.rbs +5 -0
  72. data/sig/ryac/analysis/site_bucket_mapping.rbs +26 -0
  73. data/sig/ryac/analysis/type_oracle.rbs +61 -0
  74. data/sig/ryac/ast_utils.rbs +34 -0
  75. data/sig/ryac/errors.rbs +21 -0
  76. data/sig/ryac/gem_resolver.rbs +28 -0
  77. data/sig/ryac/minifier.rbs +33 -0
  78. data/sig/ryac/name_generator.rbs +14 -0
  79. data/sig/ryac/pipeline/analyzer.rbs +34 -0
  80. data/sig/ryac/pipeline/attr_decl_shorten.rbs +17 -0
  81. data/sig/ryac/pipeline/boolean_shorten.rbs +11 -0
  82. data/sig/ryac/pipeline/char_shorten.rbs +11 -0
  83. data/sig/ryac/pipeline/compactor.rbs +125 -0
  84. data/sig/ryac/pipeline/concatenator.rbs +28 -0
  85. data/sig/ryac/pipeline/constant_aliaser.rbs +35 -0
  86. data/sig/ryac/pipeline/constant_fold.rbs +20 -0
  87. data/sig/ryac/pipeline/control_flow_simplify.rbs +34 -0
  88. data/sig/ryac/pipeline/data_types.rbs +151 -0
  89. data/sig/ryac/pipeline/endless_method.rbs +14 -0
  90. data/sig/ryac/pipeline/errors.rbs +46 -0
  91. data/sig/ryac/pipeline/file_collector.rbs +34 -0
  92. data/sig/ryac/pipeline/method_renamer.rbs +25 -0
  93. data/sig/ryac/pipeline/paren_optimizer.rbs +16 -0
  94. data/sig/ryac/pipeline/rename_patcher.rbs +38 -0
  95. data/sig/ryac/pipeline/source_patcher.rbs +12 -0
  96. data/sig/ryac/pipeline/spelling_shorten.rbs +14 -0
  97. data/sig/ryac/pipeline/stage.rbs +28 -0
  98. data/sig/ryac/pipeline/stage_runner.rbs +18 -0
  99. data/sig/ryac/pipeline/variable_renamer.rbs +21 -0
  100. data/sig/ryac/union_find.rbs +17 -0
  101. data/sig/ryac.rbs +13 -0
  102. data/sig/shims/env.rbs +19 -0
  103. data/sig/shims/typeprof/core/ast.rbs +299 -0
  104. data/sig/shims/typeprof/core/type.rbs +22 -0
  105. data/sig/shims/typeprof/core.rbs +73 -0
  106. data/tools/regen_pins.rb +45 -0
  107. metadata +201 -0
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ryac
4
+ class KeywordRenameMapping
5
+ include UnionFind
6
+
7
+ def initialize
8
+ uf_init
9
+ @methods = {}
10
+ @node_short_names = {}
11
+ @keyword_maps = {}
12
+ @frozen = false
13
+ end
14
+
15
+ def add_keyword_def(method_key, keyword_sym)
16
+ init_method(method_key)
17
+ @methods[method_key][:keywords] << keyword_sym
18
+ end
19
+
20
+ def add_keyword_call(method_key, keyword_sym, symbol_node, val_node)
21
+ init_method(method_key)
22
+ @methods[method_key][:call_entries][keyword_sym] ||= []
23
+ @methods[method_key][:call_entries][keyword_sym] << { symbol_node: symbol_node, val_node: val_node }
24
+ end
25
+
26
+ def exclude_method(method_key)
27
+ init_method(method_key)
28
+ root = uf_root(method_key)
29
+ @methods.each_key do |k|
30
+ @methods[k][:excluded] = true if uf_root(k) == root
31
+ end
32
+ end
33
+
34
+ def exclude_methods_by_mid(mids)
35
+ @methods.keys.each do |key|
36
+ exclude_method(key) if mids.include?(key[2])
37
+ end
38
+ end
39
+
40
+ def merge_groups(key1, key2)
41
+ init_method(key1)
42
+ init_method(key2)
43
+ super
44
+ end
45
+
46
+ def each_method_key(&block)
47
+ @methods.each_key(&block)
48
+ end
49
+
50
+ def assign_short_names
51
+ groups = Hash.new { |h, k| h[k] = [] } #: Hash[method_key, Array[method_key]]
52
+ @methods.each_key { |key| groups[uf_root(key)] << key }
53
+
54
+ groups.each do |_root, keys|
55
+ next if keys.any? { |k| @methods[k][:excluded] }
56
+
57
+ all_keywords = keys.flat_map { |k| @methods[k][:keywords].to_a }.uniq
58
+ total_call_entries = keys.sum { |k| @methods[k][:call_entries].values.sum(&:size) }
59
+ next if total_call_entries == 0
60
+
61
+ generator = NameGenerator.new
62
+ keyword_map = {} #: Hash[Symbol, String]
63
+ occurrences_by_sym = Hash.new(0) #: Hash[Symbol, Integer]
64
+ keys.each do |k|
65
+ info = @methods[k]
66
+ info[:keywords].each { |sym| occurrences_by_sym[sym] += 1 }
67
+ info[:call_entries].each { |sym, entries| occurrences_by_sym[sym] += entries.size }
68
+ end
69
+ # Sorted by total bytes at stake — the same greedy order every other
70
+ # rename family uses, so the shortest names go where they pay most.
71
+ all_keywords.sort_by { |sym| -(sym.to_s.size * occurrences_by_sym[sym]) }.each do |sym|
72
+ next if sym.to_s.size <= NameGenerator::KEPT_NAME_MAX
73
+
74
+ short = generator.next_name
75
+ savings = (sym.to_s.size - short.size) * occurrences_by_sym[sym]
76
+ next unless savings > MethodRenameMapping::MIN_GROUP_SAVINGS
77
+
78
+ keyword_map[sym] = short
79
+ end
80
+
81
+ next if keyword_map.empty?
82
+
83
+ @keyword_maps[_root] = keyword_map
84
+
85
+ keys.each do |key|
86
+ @methods[key][:call_entries].each do |sym, entries|
87
+ short = keyword_map[sym]
88
+ next unless short
89
+ entries.each { |e| @node_short_names[AstUtils.location_key(e[:symbol_node])] = short }
90
+ end
91
+ end
92
+ end
93
+
94
+ @frozen = true
95
+ end
96
+
97
+ def node_mapping
98
+ @node_short_names.dup
99
+ end
100
+
101
+ # Keyed by the def's location, the coordinate the scope analysis works in.
102
+ def def_node_mapping(def_node_registry)
103
+ result = {} #: Hash[location_key, Hash[Symbol, String]]
104
+ def_node_registry.each do |method_key, def_nodes|
105
+ root = uf_root(method_key)
106
+ keyword_map = @keyword_maps[root]
107
+ next unless keyword_map
108
+
109
+ next if @methods[method_key]&.[](:excluded)
110
+
111
+ def_nodes.each do |def_node|
112
+ mapping = {} #: Hash[Symbol, String]
113
+ keyword_map.each do |sym, short|
114
+ mapping[sym] = short
115
+ end
116
+ result[AstUtils.location_key(def_node)] = mapping unless mapping.empty?
117
+ end
118
+ end
119
+ result
120
+ end
121
+
122
+ # A call passing a local as a keyword value — `f(code: c)` — pays nothing
123
+ # when the local is named like the keyword, because the shorthand form
124
+ # `f(code:)` applies. These hints suggest that name to the local's scope.
125
+ #
126
+ # The scope containing the call site is the caller's concern: pass a block
127
+ # mapping a call-argument node to its scope id.
128
+ def build_variable_hints
129
+ hints = {} #: scope_mapping_table
130
+
131
+ @methods.each do |method_key, info|
132
+ next if info[:excluded]
133
+ root = uf_root(method_key)
134
+ keyword_map = @keyword_maps[root] || {}
135
+
136
+ info[:call_entries].each do |keyword_sym, entries|
137
+ # Use renamed name if available; for already-short keywords,
138
+ # use original name to preserve idempotency across re-minification passes
139
+ final_name = keyword_map[keyword_sym]
140
+ final_name ||= keyword_sym.to_s if keyword_sym.to_s.size <= NameGenerator::KEPT_NAME_MAX
141
+ next unless final_name
142
+
143
+ entries.each do |entry|
144
+ val_node = entry[:val_node]
145
+ next unless val_node.is_a?(Prism::LocalVariableReadNode)
146
+
147
+ scope_id = yield(val_node)
148
+ next unless scope_id
149
+
150
+ hints[scope_id] ||= {}
151
+ hints[scope_id][val_node.name] ||= final_name
152
+ end
153
+ end
154
+ end
155
+
156
+ hints
157
+ end
158
+
159
+ private
160
+
161
+ def init_method(method_key)
162
+ return if @methods.key?(method_key)
163
+ @methods[method_key] = { keywords: Set.new, call_entries: {}, excluded: false }
164
+ uf_add(method_key)
165
+ end
166
+ end
167
+ end