Skip to content

Commit

Permalink
Merge pull request #1470 from ksss/todo
Browse files Browse the repository at this point in the history
Implement todo option
  • Loading branch information
soutaro authored Sep 15, 2023
2 parents 008ac65 + 2ae64f8 commit 1c9b959
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 14 deletions.
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

0 comments on commit 1c9b959

Please sign in to comment.