Skip to content
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
7 changes: 6 additions & 1 deletion lib/rbs/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ def run_prototype(args, options)
require_libs = []
relative_libs = []
merge = false
todo = false
owners_included = []
outline = false

Expand Down Expand Up @@ -671,6 +672,10 @@ def run_prototype(args, options)
opts.on("--merge", "Merge generated prototype RBS with existing RBS") do
merge = true
end
opts.on("--todo", "Generates only undefined methods compared to objects") do
Warning.warn("Geneating prototypes with `--todo` option is experimental\n", category: :experimental)
todo = true
end
opts.on("--method-owner CLASS", "Generate method prototypes if the owner of the method is [CLASS]") do |klass|
owners_included << klass
end
Expand All @@ -690,7 +695,7 @@ def run_prototype(args, options)
eval("require_relative(lib)", binding, "rbs")
end

runtime = Prototype::Runtime.new(patterns: args, env: env, merge: merge, owners_included: owners_included)
runtime = Prototype::Runtime.new(patterns: args, env: env, merge: merge, todo: todo, owners_included: owners_included)
runtime.outline = outline

decls = runtime.decls
Expand Down
72 changes: 71 additions & 1 deletion lib/rbs/prototype/runtime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,60 @@
module RBS
module Prototype
class Runtime
class Todo
def initialize(builder:)
@builder = builder
end

def skip_mixin?(type_name:, module_name:, mixin_class:)
return false unless @builder.env.module_class_entry(type_name.absolute!)
return false unless @builder.env.module_class_entry(module_name.absolute!)

mixin_decls(type_name).any? do |decl|
decl.instance_of?(mixin_class) && decl.name == module_name.absolute!
end
end

def skip_singleton_method?(module_name:, name:)
return false unless @builder.env.module_class_entry(module_name.absolute!)

@builder.build_singleton(module_name.absolute!).methods.has_key?(name)
end

def skip_instance_method?(module_name:, name:)
return false unless @builder.env.module_class_entry(module_name.absolute!)

@builder.build_instance(module_name.absolute!).methods.has_key?(name)
end

def skip_constant?(module_name:, name:)
namespace = Namespace.new(path: module_name.split('::').map(&:to_sym), absolute: true)
@builder.env.constant_decl?(TypeName.new(namespace: namespace, name: name))
end

private

def mixin_decls(type_name)
type_name_absolute = type_name.absolute!
(@mixin_decls_cache ||= {}).fetch(type_name_absolute) do
@mixin_decls_cache[type_name_absolute] = @builder.env.class_decls[type_name_absolute].decls.flat_map do |d|
d.decl.members.select { |m| m.kind_of?(AST::Members::Mixin) }
end
end
end
end
private_constant :Todo

include Helpers

attr_reader :patterns
attr_reader :env
attr_reader :merge
attr_reader :todo
attr_reader :owners_included
attr_accessor :outline

def initialize(patterns:, env:, merge:, owners_included: [])
def initialize(patterns:, env:, merge:, todo: false, owners_included: [])
@patterns = patterns
@decls = nil
@modules = {}
Expand All @@ -21,6 +66,7 @@ def initialize(patterns:, env:, merge:, owners_included: [])
Object.const_get(name)
end
@outline = false
@todo = todo
end

def target?(const)
Expand All @@ -36,6 +82,10 @@ def target?(const)
end
end

def todo_object
@todo_object ||= Todo.new(builder: builder) if todo
end

def builder
@builder ||= DefinitionBuilder.new(env: env)
end
Expand Down Expand Up @@ -251,7 +301,10 @@ def target_method?(mod, instance: nil, singleton: nil)
end

def generate_methods(mod, module_name, members)
module_name_absolute = to_type_name(const_name!(mod), full_name: true).absolute!
mod.singleton_methods.select {|name| target_method?(mod, singleton: name) }.sort.each do |name|
next if todo_object&.skip_singleton_method?(module_name: module_name_absolute, name: name)

method = mod.singleton_class.instance_method(name)

if can_alias?(mod.singleton_class, method)
Expand Down Expand Up @@ -288,6 +341,8 @@ def generate_methods(mod, module_name, members)
members << AST::Members::Public.new(location: nil)

public_instance_methods.sort.each do |name|
next if todo_object&.skip_instance_method?(module_name: module_name_absolute, name: name)

method = mod.instance_method(name)

if can_alias?(mod, method)
Expand Down Expand Up @@ -322,9 +377,13 @@ def generate_methods(mod, module_name, members)

private_instance_methods = mod.private_instance_methods.select {|name| target_method?(mod, instance: name) }
unless private_instance_methods.empty?
added = false
members << AST::Members::Private.new(location: nil)

private_instance_methods.sort.each do |name|
next if todo_object&.skip_instance_method?(module_name: module_name_absolute, name: name)

added = true
method = mod.instance_method(name)

if can_alias?(mod, method)
Expand Down Expand Up @@ -355,6 +414,8 @@ def generate_methods(mod, module_name, members)
end
end
end

members.pop unless added
end
end

Expand All @@ -369,7 +430,10 @@ def generate_methods(mod, module_name, members)
end

def generate_constants(mod, decls)
module_name = const_name!(mod)
mod.constants(false).sort.each do |name|
next if todo_object&.skip_constant?(module_name: module_name, name: name)

begin
value = mod.const_get(name)
rescue StandardError, LoadError => e
Expand Down Expand Up @@ -428,6 +492,7 @@ def generate_super_class(mod)
end

def generate_class(mod)
type_name_absolute = to_type_name(const_name!(mod), full_name: true).absolute!
type_name = to_type_name(const_name!(mod))
outer_decls = ensure_outer_module_declarations(mod)

Expand All @@ -451,6 +516,8 @@ def generate_class(mod)
end

each_mixined_module(type_name, mod) do |module_name, module_full_name, mixin_class|
next if todo_object&.skip_mixin?(type_name: type_name_absolute, module_name: module_full_name, mixin_class: mixin_class)

args = type_args(module_full_name)
decl.members << mixin_class.new(
name: module_name,
Expand All @@ -474,6 +541,7 @@ def generate_module(mod)
return
end

type_name_absolute = to_type_name(name, full_name: true).absolute!
type_name = to_type_name(name)
outer_decls = ensure_outer_module_declarations(mod)

Expand All @@ -497,6 +565,8 @@ def generate_module(mod)
end

each_mixined_module(type_name, mod) do |module_name, module_full_name, mixin_class|
next if todo_object&.skip_mixin?(type_name: type_name_absolute, module_name: module_full_name, mixin_class: mixin_class)

args = type_args(module_full_name)
decl.members << mixin_class.new(
name: module_name,
Expand Down
40 changes: 28 additions & 12 deletions sig/prototype/runtime.rbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
module RBS
module Prototype
class Runtime
class Todo
@builder: DefinitionBuilder

@mixin_decls_cache: Hash[TypeName, Array[untyped]]

def initialize: (builder: DefinitionBuilder) -> void

def skip_mixin?: (type_name: TypeName, module_name: TypeName, mixin_class: mixin_class) -> bool

def skip_singleton_method?: (module_name: TypeName, name: Symbol) -> bool

def skip_instance_method?: (module_name: TypeName, name: Symbol) -> bool

def skip_constant?: (module_name: String, name: Symbol) -> bool

private def mixin_decls: (TypeName type_name) -> Array[AST::Members::Include | AST::Members::Extend | AST::Members::Prepend]
end

type mixin_class = singleton(AST::Members::Include) | singleton(AST::Members::Prepend) | singleton(AST::Members::Extend)

@decls: Array[AST::Declarations::t]?

@modules: Hash[String, Module]
Expand All @@ -10,6 +30,8 @@ module RBS
@module_name_method: UnboundMethod
@object_class: UnboundMethod

@todo_object: Todo?

include Helpers

attr_reader patterns: Array[String]
Expand All @@ -18,14 +40,18 @@ module RBS

attr_reader merge: bool

attr_reader todo: bool

attr_accessor outline: bool

attr_reader owners_included: Array[Module]

def initialize: (patterns: Array[String], env: Environment, merge: bool, ?owners_included: Array[Symbol]) -> void
def initialize: (patterns: Array[String], env: Environment, merge: bool, ?todo: bool, ?owners_included: Array[Symbol]) -> void

def target?: (Module const) -> bool

def todo_object: () -> Todo?

def builder: () -> DefinitionBuilder

def parse: (String file) -> void
Expand All @@ -34,17 +60,7 @@ module RBS

def to_type_name: (String name, ?full_name: bool) -> TypeName

interface _MixinFactory
def new: (
name: TypeName,
args: Array[Types::t],
location: Location[untyped, untyped]?,
comment: AST::Comment?,
annotations: Array[AST::Annotation]
) -> (AST::Members::Include | AST::Members::Prepend | AST::Members::Extend)
end

def each_mixined_module: (TypeName type_name, Module mod) { (TypeName, TypeName, _MixinFactory) -> void } -> void
def each_mixined_module: (TypeName type_name, Module mod) { (TypeName, TypeName, mixin_class) -> void } -> void

def each_mixined_module_one: (TypeName type_name, Module mod) { (TypeName, TypeName, bool) -> void } -> void

Expand Down
22 changes: 22 additions & 0 deletions test/rbs/cli_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,28 @@ module A
end
end

def test_prototype__runtime__todo
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
with_cli do |cli|
begin
old = $stderr
$stderr = cli.stderr
cli.run(%w(prototype runtime --todo ::Object))
ensure
$stderr = old
end

assert_equal <<~EOM, cli.stdout.string
EOM

assert_match Regexp.new(Regexp.escape "Geneating prototypes with `--todo` option is experimental"), cli.stderr.string
end
end
end
end


def test_test
Dir.mktmpdir do |dir|
dir = Pathname(dir)
Expand Down
85 changes: 85 additions & 0 deletions test/rbs/runtime_prototype_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -606,4 +606,89 @@ def baz: () -> untyped
end
end
end

class TodoClass
module MixinDefined
end
include MixinDefined
module MixinTodo
end
extend MixinTodo

def public_defined; end
def public_todo; end
private def private_defined; end
private def private_todo; end
def self.singleton_defined; end
def self.singleton_todo; end

CONST_DEFINED = 1
CONST_TODO = 1
end

module TodoModule
def public_defined; end
def public_todo; end
end

def test_todo
SignatureManager.new do |manager|
manager.files[Pathname("foo.rbs")] = <<~RBS
module RBS
class RuntimePrototypeTest < ::Test::Unit::TestCase
class TodoClass
module MixinDefined
end
include MixinDefined
def public_defined: () -> void
private def private_defined: () -> void
def self.singleton_defined: () -> void
CONST_DEFINED: Integer
end
module TodoModule
def public_defined: () -> void
end
end
end
RBS

manager.build do |env|
p = Runtime.new(patterns: ["RBS::RuntimePrototypeTest::TodoClass"], env: env, merge: false, todo: true)
assert_write p.decls, <<~RBS
module RBS
class RuntimePrototypeTest < ::Test::Unit::TestCase
class TodoClass
extend RBS::RuntimePrototypeTest::TodoClass::MixinTodo

def self.singleton_todo: () -> untyped

public

def public_todo: () -> untyped

private

def private_todo: () -> untyped

CONST_TODO: ::Integer
end
end
end
RBS

p = Runtime.new(patterns: ["RBS::RuntimePrototypeTest::TodoModule"], env: env, merge: false, todo: true)
assert_write p.decls, <<~RBS
module RBS
class RuntimePrototypeTest < ::Test::Unit::TestCase
module TodoModule
public

def public_todo: () -> untyped
end
end
end
RBS
end
end
end
end