Skip to content
Open
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
28 changes: 7 additions & 21 deletions lib/rbs/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,6 @@ def parse_logging_options(opts)
opts
end

def has_parser?
defined?(RubyVM::AbstractSyntaxTree) ? true : false
end

def run(args)
@original_args = args.dup

Expand Down Expand Up @@ -683,10 +679,6 @@ def autoload(name, path)
end

def run_prototype_file(format, args)
availability = unless has_parser?
"\n** This command does not work on this interpreter (#{RUBY_ENGINE}) **\n"
end

# @type var output_dir: Pathname?
output_dir = nil
# @type var base_dir: Pathname?
Expand All @@ -697,7 +689,7 @@ def run_prototype_file(format, args)
opts = OptionParser.new
opts.banner = <<EOU
Usage: rbs prototype #{format} [files...]
#{availability}

Generate RBS prototype from source code.
It parses specified Ruby code and and generates RBS prototypes.

Expand Down Expand Up @@ -728,11 +720,6 @@ def run_prototype_file(format, args)

opts.parse!(args)

unless has_parser?
stdout.puts "Not supported on this interpreter (#{RUBY_ENGINE})."
return 1
end

if args.empty?
stdout.puts opts
return 1
Expand All @@ -741,9 +728,9 @@ def run_prototype_file(format, args)
new_parser = -> do
case format
when "rbi"
Prototype::RBI.new()
Prototype::RBI
when "rb"
Prototype::RB.new()
Prototype::RB
else
raise
end
Expand Down Expand Up @@ -796,7 +783,7 @@ def run_prototype_file(format, args)

parser = new_parser[]
begin
parser.parse file_path.read()
decls = parser.parse file_path.read()
rescue SyntaxError
stdout.puts " ⚠️ Unable to parse due to SyntaxError: `#{file_path}`"
next
Expand All @@ -817,7 +804,7 @@ def run_prototype_file(format, args)
(output_path.parent).mkpath
output_path.open("w") do |io|
writer = Writer.new(out: io)
writer.write(parser.decls)
writer.write(decls)
end
end
end
Expand All @@ -837,13 +824,12 @@ def run_prototype_file(format, args)
else
# file mode
parser = new_parser[]
writer = Writer.new(out: stdout)

input_paths.each do |file|
parser.parse file.read()
writer.write parser.parse(file.read())
end

writer = Writer.new(out: stdout)
writer.write parser.decls
end

0
Expand Down
167 changes: 17 additions & 150 deletions lib/rbs/prototype/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,160 +5,27 @@ module Prototype
module Helpers
private

def parse_comments(string, include_trailing:)
Prism.parse_comments(string, version: "current").yield_self do |prism_comments| # steep:ignore UnexpectedKeywordArgument
prism_comments.each_with_object({}) do |comment, hash| #$ Hash[Integer, AST::Comment]
# Skip EmbDoc comments
next unless comment.is_a?(Prism::InlineComment)
# skip like `module Foo # :nodoc:`
next if comment.trailing? && !include_trailing

line = comment.location.start_line
body = "#{comment.location.slice}\n"
body = body[2..-1] or raise
body = "\n" if body.empty?

comment = AST::Comment.new(string: body, location: nil)
if prev_comment = hash.delete(line - 1)
hash[line] = AST::Comment.new(string: prev_comment.string + comment.string,
location: nil)
else
hash[line] = comment
end
end
end
end

def block_from_body(node)
_, args_node, body_node = node.children
_pre_num, _pre_init, _opt, _first_post, _post_num, _post_init, _rest, _kw, _kwrest, block_var = args_from_node(args_node)

# @type var body_node: node?
if body_node
yields = any_node?(body_node) {|n| n.type == :YIELD }
end

if yields || block_var
required = true

if body_node
if any_node?(body_node) {|n| n.type == :FCALL && n.children[0] == :block_given? && !n.children[1] }
required = false
end
end

if _rest == :* && block_var == :&
# ... is given
required = false
end

if block_var
if body_node
usage = NodeUsage.new(body_node)
if usage.each_conditional_node.any? {|n| n.type == :LVAR && n.children[0] == block_var }
required = false
end
end
end

if yields
function = Types::Function.empty(untyped)

yields.each do |yield_node|
array_content = yield_node.children[0]&.children&.compact || []

# @type var keywords: node?
positionals, keywords = if keyword_hash?(array_content.last)
[array_content.take(array_content.size - 1), array_content.last]
else
[array_content, nil]
end

if (diff = positionals.size - function.required_positionals.size) > 0
diff.times do
function.required_positionals << Types::Function::Param.new(
type: untyped,
name: nil
)
end
end

if keywords
keywords.children[0].children.each_slice(2) do |key_node, value_node|
if key_node
key = key_node.children[0]
function.required_keywords[key] ||=
Types::Function::Param.new(
type: untyped,
name: nil
)
end
end
end
end
def process_comments(comments, include_trailing:)
comments.each_with_object({}) do |comment, hash| #$ Hash[Integer, AST::Comment]
# Skip EmbDoc comments
next unless comment.is_a?(Prism::InlineComment)
# skip like `module Foo # :nodoc:`
next if comment.trailing? && !include_trailing

line = comment.location.start_line
body = "#{comment.slice}\n"
body = body[2..-1] or raise
body = "\n" if body.empty?

comment = AST::Comment.new(string: body, location: nil)
if prev_comment = hash.delete(line - 1)
hash[line] = AST::Comment.new(string: prev_comment.string + comment.string,
location: nil)
else
function = Types::UntypedFunction.new(return_type: untyped)
hash[line] = comment
end


Types::Block.new(required: required, type: function, self_type: nil)
end
end

def each_child(node, &block)
each_node node.children, &block
end

def each_node(nodes)
nodes.each do |child|
if child.is_a?(RubyVM::AbstractSyntaxTree::Node)
yield child
end
end
end

def any_node?(node, nodes: [], &block)
if yield(node)
nodes << node
end

each_child node do |child|
any_node? child, nodes: nodes, &block
end

nodes.empty? ? nil : nodes
end

def keyword_hash?(node)
if node && node.type == :HASH
node.children[0].children.compact.each_slice(2).all? {|key, _|
symbol_literal_node?(key)
}
else
false
end
end

# NOTE: args_node may be a nil by a bug
# https://bugs.ruby-lang.org/issues/17495
def args_from_node(args_node)
args_node&.children || [0, nil, nil, nil, 0, nil, nil, nil, nil, nil]
end

def symbol_literal_node?(node)
case node.type
when :LIT
if node.children[0].is_a?(Symbol)
node.children[0]
end
when :SYM
node.children[0]
end
end

def untyped
@untyped ||= Types::Bases::Any.new(location: nil)
end
end
end
end
97 changes: 41 additions & 56 deletions lib/rbs/prototype/node_usage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,69 +27,54 @@ def calculate(node, conditional:)
conditional_nodes << node
end

case node.type
when :IF, :UNLESS
cond_node, true_node, false_node = node.children
calculate(cond_node, conditional: true)
calculate(true_node, conditional: conditional) if true_node
calculate(false_node, conditional: conditional) if false_node
when :AND, :OR
left, right = node.children
calculate(left, conditional: true)
calculate(right, conditional: conditional)
when :QCALL
receiver, _, args = node.children
calculate(receiver, conditional: true)
calculate(args, conditional: false) if args
when :WHILE
cond, body = node.children
calculate(cond, conditional: true)
calculate(body, conditional: false) if body
when :OP_ASGN_OR, :OP_ASGN_AND
var, _, asgn = node.children
calculate(var, conditional: true)
calculate(asgn, conditional: conditional)
when :LASGN, :IASGN, :GASGN
_, lhs = node.children
calculate(lhs, conditional: conditional) if lhs
when :MASGN
lhs, _ = node.children
calculate(lhs, conditional: conditional)
when :CDECL
if node.children.size == 2
_, lhs = node.children
calculate(lhs, conditional: conditional)
else
const, _, lhs = node.children
calculate(const, conditional: false)
calculate(lhs, conditional: conditional)
end
when :SCOPE
_, _, body = node.children
calculate(body, conditional: conditional)
when :CASE2
_, *branches = node.children
branches.each do |branch|
if branch.type == :WHEN
list, body = branch.children
list.children.each do |child|
if child
calculate(child, conditional: true)
end
end
calculate(body, conditional: conditional)
else
calculate(branch, conditional: conditional)
case node
in Prism::IfNode
calculate(node.predicate, conditional: true)
calculate(node.statements, conditional: conditional) if node.statements
calculate(node.subsequent, conditional: conditional) if node.subsequent
in Prism::UnlessNode
calculate(node.predicate, conditional: true)
calculate(node.statements, conditional: conditional) if node.statements
calculate(node.else_clause, conditional: conditional) if node.else_clause
in Prism::AndNode | Prism::OrNode
calculate(node.left, conditional: true)
calculate(node.right, conditional: conditional)
in Prism::CallNode if node.safe_navigation?
calculate(node.receiver, conditional: true) if node.receiver
calculate(node.arguments, conditional: false) if node.arguments
in Prism::WhileNode
calculate(node.predicate, conditional: true)
calculate(node.statements, conditional: false) if node.statements
in Prism::ConstantOrWriteNode | Prism::ConstantAndWriteNode |
Prism::GlobalVariableOrWriteNode | Prism::GlobalVariableAndWriteNode |
Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableAndWriteNode |
Prism::LocalVariableOrWriteNode | Prism::LocalVariableAndWriteNode
conditional_nodes << node
calculate(node.value, conditional: conditional)
in Prism::ConstantWriteNode | Prism::MultiWriteNode |
Prism::LocalVariableWriteNode | Prism::InstanceVariableWriteNode | Prism::GlobalVariableWriteNode
calculate(node.value, conditional: conditional)
in Prism::ConstantPathWriteNode
calculate(node.target, conditional: false)
calculate(node.value, conditional: conditional)
in Prism::BlockNode | Prism::ClassNode | Prism::DefNode | Prism::LambdaNode | Prism::ModuleNode | Prism::SingletonClassNode
# Anything with locals
calculate(node.body, conditional: conditional) if node.body
in Prism::CaseNode[predicate: predicate] unless predicate
node.conditions.each do |when_node|
when_node.conditions.each do |child|
calculate(child, conditional: true)
end
calculate(when_node.statements, conditional: conditional) if when_node.statements
end
when :BLOCK
*nodes, last = node.children
in Prism::StatementsNode
*nodes, last = node.body
nodes.each do |no|
calculate(no, conditional: false)
end
calculate(last, conditional: conditional) if last
else
each_child(node) do |child|
node.compact_child_nodes.each do |child|
calculate(child, conditional: false)
end
end
Expand Down
Loading
Loading