Skip to content

Commit d321f86

Browse files
committed
Rewrite attr_* into def methods
1 parent 556e965 commit d321f86

File tree

4 files changed

+495
-0
lines changed

4 files changed

+495
-0
lines changed

lib/rbi.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
require "rbi/rewriters/nest_non_public_methods"
1717
require "rbi/rewriters/group_nodes"
1818
require "rbi/rewriters/remove_known_definitions"
19+
require "rbi/rewriters/replace_attributes_with_methods"
1920
require "rbi/rewriters/sort_nodes"
2021
require "rbi/parser"
2122
require "rbi/printer"

lib/rbi/model.rb

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ def replace(node)
4242
self.parent_tree = nil
4343
end
4444

45+
sig { params(nodes: T::Enumerable[Node]).void }
46+
def replace_with_multiple(nodes)
47+
tree = parent_tree
48+
raise unless tree
49+
50+
# Does this work?
51+
nodes.each { |node| tree << node }
52+
detach
53+
end
54+
4555
sig { returns(T.nilable(Scope)) }
4656
def parent_scope
4757
parent = T.let(parent_tree, T.nilable(Tree))
@@ -1153,6 +1163,43 @@ def initialize(
11531163
block&.call(self)
11541164
end
11551165

1166+
sig do
1167+
params(
1168+
params: T::Array[SigParam],
1169+
return_type: T.nilable(String),
1170+
is_abstract: T::Boolean,
1171+
is_override: T::Boolean,
1172+
is_overridable: T::Boolean,
1173+
is_final: T::Boolean,
1174+
type_params: T::Array[String],
1175+
checked: T.nilable(Symbol),
1176+
loc: T.nilable(Loc),
1177+
).returns(Sig)
1178+
end
1179+
def new_with(
1180+
params: @params,
1181+
return_type: @return_type,
1182+
is_abstract: @is_abstract,
1183+
is_override: @is_override,
1184+
is_overridable: @is_overridable,
1185+
is_final: @is_final,
1186+
type_params: @type_params.dup,
1187+
checked: @checked,
1188+
loc: @loc.dup
1189+
)
1190+
Sig.new(
1191+
params: params,
1192+
return_type: return_type,
1193+
is_abstract: is_abstract,
1194+
is_override: is_override,
1195+
is_overridable: is_overridable,
1196+
is_final: is_final,
1197+
type_params: type_params,
1198+
checked: checked,
1199+
loc: loc,
1200+
)
1201+
end
1202+
11561203
sig { params(param: SigParam).void }
11571204
def <<(param)
11581205
@params << param
@@ -1171,6 +1218,15 @@ def ==(other)
11711218
is_override == other.is_override && is_overridable == other.is_overridable && is_final == other.is_final &&
11721219
type_params == other.type_params && checked == other.checked
11731220
end
1221+
1222+
sig { override.returns(String) }
1223+
def inspect
1224+
io = StringIO.new
1225+
1226+
Printer.new(out: io, indent: 0).visit(self)
1227+
1228+
io.string.chomp
1229+
end
11741230
end
11751231

11761232
class SigParam < NodeWithComments
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module RBI
5+
module TempHelpers
6+
extend T::Sig
7+
end
8+
9+
module Rewriters
10+
class ReplaceAttributesWithMethods < Visitor
11+
extend T::Sig
12+
include TempHelpers
13+
14+
sig { override.params(node: T.nilable(Node)).void }
15+
def visit(node)
16+
return unless node
17+
18+
case node
19+
when Tree
20+
node.nodes.dup.each do |child|
21+
visit(child)
22+
next unless (attr = child).is_a?(Attr)
23+
24+
new_methods = convert_to_methods(attr)
25+
26+
child.replace_with_multiple(new_methods)
27+
end
28+
end
29+
end
30+
31+
private
32+
33+
sig { params(attr: Attr).returns(T::Array[Method]) }
34+
def convert_to_methods(attr)
35+
sig, attribute_type = parse_sig_of(attr)
36+
37+
case attr
38+
when AttrReader then convert_attr_reader_to_methods(attr, sig, attribute_type)
39+
when AttrWriter then convert_attr_writer_to_methods(attr, sig, attribute_type)
40+
when AttrAccessor then convert_attr_accessor_to_methods(attr, sig, attribute_type)
41+
else raise NotImplementedError, "Unknown attribute type: #{attr.class}"
42+
end
43+
end
44+
45+
sig { params(attr: AttrReader, sig: T.nilable(Sig), attribute_type: T.nilable(String)).returns(T::Array[Method]) }
46+
def convert_attr_reader_to_methods(attr, sig, attribute_type)
47+
attr.names.map do |name|
48+
create_getter_method(name.to_s, sig, attr.visibility, attr.loc, attr.comments)
49+
end
50+
end
51+
52+
sig { params(attr: AttrWriter, sig: T.nilable(Sig), attribute_type: T.nilable(String)).returns(T::Array[Method]) }
53+
def convert_attr_writer_to_methods(attr, sig, attribute_type)
54+
attr.names.map do |name|
55+
create_setter_method(name.to_s, sig, attribute_type, attr.visibility, attr.loc, attr.comments)
56+
end
57+
end
58+
59+
sig do
60+
params(attr: AttrAccessor, sig: T.nilable(Sig), attribute_type: T.nilable(String)).returns(T::Array[Method])
61+
end
62+
def convert_attr_accessor_to_methods(attr, sig, attribute_type)
63+
readers = attr.names.flat_map do |name|
64+
create_getter_method(name.to_s, sig, attr.visibility, attr.loc, attr.comments)
65+
end
66+
67+
writers = attr.names.map do |name|
68+
create_setter_method(name.to_s, sig, attribute_type, attr.visibility, attr.loc, attr.comments)
69+
end
70+
71+
readers + writers
72+
end
73+
74+
sig { params(attr: Attr).returns([T.nilable(Sig), T.nilable(String)]) }
75+
def parse_sig_of(attr)
76+
raise "Attributes cannot have more than 1 sig" if 1 < attr.sigs.count
77+
78+
sig = attr.sigs.first
79+
return [nil, nil] unless sig
80+
81+
attribute_type = case attr
82+
when AttrReader, AttrAccessor then sig.return_type
83+
when AttrWriter then sig.params.first&.type
84+
end
85+
86+
[sig, attribute_type]
87+
end
88+
89+
sig do
90+
params(
91+
name: String,
92+
sig: T.nilable(Sig),
93+
visibility: Visibility,
94+
loc: T.nilable(Loc),
95+
comments: T::Array[Comment],
96+
).returns(Method)
97+
end
98+
def create_getter_method(name, sig, visibility, loc, comments)
99+
Method.new(
100+
name,
101+
params: [],
102+
visibility: visibility,
103+
sigs: sig ? [sig] : [],
104+
loc: loc,
105+
comments: comments,
106+
)
107+
end
108+
109+
sig do
110+
params(
111+
name: String,
112+
sig: T.nilable(Sig),
113+
attribute_type: T.nilable(String),
114+
visibility: Visibility,
115+
loc: T.nilable(Loc),
116+
comments: T::Array[Comment],
117+
).returns(Method)
118+
end
119+
def create_setter_method(name, sig, attribute_type, visibility, loc, comments) # rubocop:disable Metrics/ParameterLists
120+
sig = if sig # Modify the original sig to correct the name, and remove the return type
121+
params = attribute_type ? [SigParam.new(name, attribute_type)] : []
122+
sig.new_with(params: params, return_type: "void")
123+
end
124+
125+
Method.new(
126+
"#{name}=",
127+
params: [ReqParam.new(name)],
128+
visibility: visibility,
129+
sigs: sig ? [sig] : sig,
130+
loc: loc,
131+
comments: comments,
132+
)
133+
end
134+
end
135+
end
136+
137+
class Tree
138+
extend T::Sig
139+
140+
sig { void }
141+
def replace_attributes_with_methods!
142+
visitor = Rewriters::ReplaceAttributesWithMethods.new
143+
visitor.visit(self)
144+
end
145+
end
146+
end

0 commit comments

Comments
 (0)