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 18, 2024
1 parent 799ce4f commit ae559a7
Show file tree
Hide file tree
Showing 4 changed files with 484 additions and 1 deletion.
5 changes: 4 additions & 1 deletion lib/rbi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
require "stringio"

module RBI
class Error < StandardError; end
class Error < StandardError
extend T::Sig
end
end

require "rbi/loc"
Expand All @@ -21,6 +23,7 @@ class Error < StandardError; end
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
3 changes: 3 additions & 0 deletions lib/rbi/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,9 @@ def initialize(name, names, visibility: Public.new, sigs: [], loc: nil, comments

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

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

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

module RBI
class UnexpectedMultipleSigsError < Error
attr_reader :node

sig { params(node: Node).void }
def initialize(node)
super(<<~MSG)
This declaration cannot have more than one sig.
#{node.string.chomp}
MSG

@node = node
end
end

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.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 ReplaceNodeError, "Can't replace #{self} without a parent tree" unless tree

node.detach
with.each { |node| tree << node }
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 UnexpectedMultipleSigsError.new(self) 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 ae559a7

Please sign in to comment.