Skip to content

Commit

Permalink
Rewrite attr_* into def methods
Browse files Browse the repository at this point in the history
  • Loading branch information
amomchilov committed Jun 17, 2024
1 parent 556e965 commit 866256c
Show file tree
Hide file tree
Showing 4 changed files with 448 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/rbi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
require "rbi/rewriters/nest_non_public_methods"
require "rbi/rewriters/group_nodes"
require "rbi/rewriters/remove_known_definitions"
require "rbi/rewriters/attr_to_methods"
require "rbi/rewriters/sort_nodes"
require "rbi/parser"
require "rbi/printer"
Expand Down
4 changes: 4 additions & 0 deletions lib/rbi/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,10 @@ def initialize(name, names, visibility: Public.new, sigs: [], loc: nil, comments

sig { abstract.returns(T::Array[String]) }
def fully_qualified_names; end

# Convert this attribute declaration into method declarations, adjusting the sigs as necessary.
sig { abstract.returns(T::Array[Method]) }
def convert_to_methods; end
end

class AttrAccessor < Attr
Expand Down
151 changes: 151 additions & 0 deletions lib/rbi/rewriters/attr_to_methods.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# typed: strict
# frozen_string_literal: true

module RBI
module Rewriters
class AttrToMethods < Visitor
extend T::Sig

sig { override.params(node: T.nilable(Node)).void }
def visit(node)
case node
when Tree
visit_all(node.nodes) # Why doesn't this need a dup?!?!?!?

when Attr
new_methods = node.convert_to_methods
replace(node, with: new_methods)
end
end

private

sig { params(node: Node, with: T::Array[Node]).void }
def replace(node, with:)
tree = node.parent_tree
raise unless tree

with.each { |node| tree << node }
node.detach
end
end
end

class Tree
extend T::Sig

sig { void }
def replace_attributes_with_methods!
visitor = Rewriters::AttrToMethods.new
visitor.visit(self)
end
end

class Attr
private

sig(:final) { returns([T.nilable(Sig), T.nilable(String)]) }
def parse_sig
raise "Attributes cannot have more than 1 sig" if 1 < sigs.count

sig = sigs.first
return [nil, nil] unless sig

attribute_type = case self
when AttrReader, AttrAccessor then sig.return_type
when AttrWriter then sig.params.first&.type
end

[sig, attribute_type]
end

sig do
params(
name: String,
sig: T.nilable(Sig),
visibility: Visibility,
loc: T.nilable(Loc),
comments: T::Array[Comment],
).returns(Method)
end
def create_getter_method(name, sig, visibility, loc, comments)
Method.new(
name,
params: [],
visibility: visibility,
sigs: sig ? [sig] : [],
loc: loc,
comments: comments,
)
end

sig do
params(
name: String,
sig: T.nilable(Sig),
attribute_type: T.nilable(String),
visibility: Visibility,
loc: T.nilable(Loc),
comments: T::Array[Comment],
).returns(Method)
end
def create_setter_method(name, sig, attribute_type, visibility, loc, comments) # rubocop:disable Metrics/ParameterLists
sig = if sig # Modify the original sig to correct the name, and remove the return type
params = attribute_type ? [SigParam.new(name, attribute_type)] : []

Sig.new(
params: params,
return_type: "void",
is_abstract: sig.is_abstract,
is_override: sig.is_override,
is_overridable: sig.is_overridable,
is_final: sig.is_final,
type_params: sig.type_params,
checked: sig.checked,
loc: sig.loc,
)
end

Method.new(
"#{name}=",
params: [ReqParam.new(name)],
visibility: visibility,
sigs: sig ? [sig] : [],
loc: loc,
comments: comments,
)
end
end

class AttrAccessor
sig { override.returns(T::Array[Method]) }
def convert_to_methods
sig, attribute_type = parse_sig

names.flat_map do |name|
[
create_getter_method(name.to_s, sig, visibility, loc, comments),
create_setter_method(name.to_s, sig, attribute_type, visibility, loc, comments),
]
end
end
end

class AttrReader
sig { override.returns(T::Array[Method]) }
def convert_to_methods
sig, _ = parse_sig

names.map { |name| create_getter_method(name.to_s, sig, visibility, loc, comments) }
end
end

class AttrWriter
sig { override.returns(T::Array[Method]) }
def convert_to_methods
sig, attribute_type = parse_sig

names.map { |name| create_setter_method(name.to_s, sig, attribute_type, visibility, loc, comments) }
end
end
end
Loading

0 comments on commit 866256c

Please sign in to comment.