Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rewriter to transform attributes into methods #308

Merged
merged 1 commit into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
169 changes: 169 additions & 0 deletions lib/rbi/rewriters/attr_to_methods.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# typed: strict
# frozen_string_literal: true

module RBI
class UnexpectedMultipleSigsError < Error
sig { returns(Node) }
attr_reader :node
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
attr_reader :node
sig { returns(Node) }
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)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

when Attr
replace(node, with: node.convert_to_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
sig { abstract.returns(T::Array[Method]) }
def convert_to_methods; end

private

sig(:final) { returns([T.nilable(Sig), T.nilable(String)]) }
def parse_sig
raise UnexpectedMultipleSigsError, 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
Loading