Skip to content

Commit a501a77

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

File tree

4 files changed

+485
-0
lines changed

4 files changed

+485
-0
lines changed

lib/rbi.rb

+1
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/attr_to_methods"
1920
require "rbi/rewriters/sort_nodes"
2021
require "rbi/parser"
2122
require "rbi/printer"

lib/rbi/model.rb

+42
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,43 @@ def initialize(
11531153
block&.call(self)
11541154
end
11551155

1156+
sig do
1157+
params(
1158+
params: T::Array[SigParam],
1159+
return_type: T.nilable(String),
1160+
is_abstract: T::Boolean,
1161+
is_override: T::Boolean,
1162+
is_overridable: T::Boolean,
1163+
is_final: T::Boolean,
1164+
type_params: T::Array[String],
1165+
checked: T.nilable(Symbol),
1166+
loc: T.nilable(Loc),
1167+
).returns(Sig)
1168+
end
1169+
def new_with(
1170+
params: @params,
1171+
return_type: @return_type,
1172+
is_abstract: @is_abstract,
1173+
is_override: @is_override,
1174+
is_overridable: @is_overridable,
1175+
is_final: @is_final,
1176+
type_params: @type_params.dup,
1177+
checked: @checked,
1178+
loc: @loc.dup
1179+
)
1180+
Sig.new(
1181+
params: params,
1182+
return_type: return_type,
1183+
is_abstract: is_abstract,
1184+
is_override: is_override,
1185+
is_overridable: is_overridable,
1186+
is_final: is_final,
1187+
type_params: type_params,
1188+
checked: checked,
1189+
loc: loc,
1190+
)
1191+
end
1192+
11561193
sig { params(param: SigParam).void }
11571194
def <<(param)
11581195
@params << param
@@ -1171,6 +1208,11 @@ def ==(other)
11711208
is_override == other.is_override && is_overridable == other.is_overridable && is_final == other.is_final &&
11721209
type_params == other.type_params && checked == other.checked
11731210
end
1211+
1212+
sig { override.returns(String) }
1213+
def inspect
1214+
string.chomp
1215+
end
11741216
end
11751217

11761218
class SigParam < NodeWithComments

lib/rbi/rewriters/attr_to_methods.rb

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

0 commit comments

Comments
 (0)