Skip to content

Commit

Permalink
Merge pull request #2257 from ksss/test-return-nil
Browse files Browse the repository at this point in the history
[rbs/unit_test] Treat nil as a return value
  • Loading branch information
soutaro authored Jan 28, 2025
2 parents d70b6ee + d59fa39 commit 964066e
Show file tree
Hide file tree
Showing 13 changed files with 66 additions and 46 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ group :libs do
gem "dbm"
gem "mutex_m"
gem "nkf"
gem "pathname"
end

group :profilers do
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ GEM
parser (3.3.7.0)
ast (~> 2.4.1)
racc
pathname (0.4.0)
power_assert (2.0.5)
pstore (0.1.4)
psych (4.0.6)
Expand Down Expand Up @@ -177,6 +178,7 @@ DEPENDENCIES
net-smtp
nkf
ostruct
pathname
pstore
raap
rake
Expand Down
2 changes: 1 addition & 1 deletion core/gc.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ module GC
# `:HAVE_FINALIZE`
# :
#
def self.raw_data: () -> Array[Hash[Symbol, untyped]]
def self.raw_data: () -> Array[Hash[Symbol, untyped]]?

# <!--
# rdoc-file=gc.c
Expand Down
12 changes: 7 additions & 5 deletions lib/rbs/test/type_check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ def initialize(self_class:, builder:, sample_size:, unchecked_classes:, instance
end

def overloaded_call(method, method_name, call, errors:)
es = method.method_types.map do |method_type|
es = method_call(method_name, method_type, call, errors: [])
es = method.defs.map do |type_def|
es = method_call(method_name, type_def.type, call, errors: [], annotations: type_def.annotations)

if es.empty?
return errors
Expand Down Expand Up @@ -58,11 +58,11 @@ def overloaded_call(method, method_name, call, errors:)
errors
end

def method_call(method_name, method_type, call, errors:)
def method_call(method_name, method_type, call, errors:, annotations: [])
return errors if method_type.type.is_a?(Types::UntypedFunction)

args(method_name, method_type, method_type.type, call.method_call, errors, type_error: Errors::ArgumentTypeError, argument_error: Errors::ArgumentError)
self.return(method_name, method_type, method_type.type, call.method_call, errors, return_error: Errors::ReturnTypeError)
self.return(method_name, method_type, method_type.type, call.method_call, errors, return_error: Errors::ReturnTypeError, annotations:)

if method_type.block
case
Expand Down Expand Up @@ -106,8 +106,10 @@ def args(method_name, method_type, fun, call, errors, type_error:, argument_erro
end
end

def return(method_name, method_type, fun, call, errors, return_error:)
def return(method_name, method_type, fun, call, errors, return_error:, annotations: [])
if call.return?
return if Test.call(call.return_value, IS_AP, NilClass) && annotations.find { |a| a.string == "implicitly-returns-nil" }

unless value(call.return_value, fun.return_type)
errors << return_error.new(klass: self_class,
method_name: method_name,
Expand Down
6 changes: 4 additions & 2 deletions lib/rbs/unit_test/spy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class WrapSpy
attr_reader :object
attr_reader :method_name

NO_RETURN = Object.new

def initialize(object:, method_name:)
@callback = -> (_) { }
@object = object
Expand All @@ -39,7 +41,7 @@ def wrapped_object
define_method(
spy.method_name,
_ = -> (*args, &block) do
return_value = nil
return_value = NO_RETURN
exception = nil
block_calls = [] #: Array[Test::ArgumentsReturn]

Expand Down Expand Up @@ -105,7 +107,7 @@ def wrapped_object
arguments: args,
exception: exception
)
when return_value
when ::RBS::UnitTest::Spy::WrapSpy::NO_RETURN != return_value
Test::ArgumentsReturn.return(
arguments: args,
value: return_value
Expand Down
28 changes: 17 additions & 11 deletions lib/rbs/unit_test/type_assertions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def send_setup(method_type, receiver, method, args, proc)
end
end

last_trace = trace.last or raise
last_trace = trace.last or raise "empty trace"

yield(mt, last_trace, result, exception)
end
Expand All @@ -182,9 +182,9 @@ def send_setup(method_type, receiver, method, args, proc)

assert_empty errors.map {|x| RBS::Test::Errors.to_string(x) }, "Call trace does not match with given method type: #{trace.inspect}"

method_types = method_types(method)
all_errors = method_types.map {|t| typecheck.method_call(method, t, trace, errors: []) }
assert all_errors.any? {|es| es.empty? }, "Call trace does not match one of method definitions:\n #{trace.inspect}\n #{method_types.join(" | ")}"
method_defs = method_defs(method)
all_errors = method_defs.map {|t| typecheck.method_call(method, t.type, trace, errors: [], annotations: t.annotations) }
assert all_errors.any? {|es| es.empty? }, "Call trace does not match one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}"

raise exception if exception

Expand Down Expand Up @@ -219,30 +219,36 @@ def send_setup(method_type, receiver, method, args, proc)
assert_operator exception, :is_a?, ::Exception
assert_empty errors.map {|x| RBS::Test::Errors.to_string(x) }

method_types = method_types(method)
all_errors = method_types.map {|t| typecheck.method_call(method, t, trace, errors: []) }
assert all_errors.all? {|es| es.size > 0 }, "Call trace unexpectedly matches one of method definitions:\n #{trace.inspect}\n #{method_types.join(" | ")}"
method_defs = method_defs(method)
all_errors = method_defs.map {|t| typecheck.method_call(method, t.type, trace, errors: [], annotations: t.annotations) }
assert all_errors.all? {|es| es.size > 0 }, "Call trace unexpectedly matches one of method definitions:\n #{trace.inspect}\n #{method_defs.map(&:type).join(" | ")}"

result
end
end

def method_types(method)
def method_defs(method)
type, definition = target

case type
when Types::ClassInstance
subst = RBS::Substitution.build(definition.type_params, type.args)
definition.methods[method].method_types.map do |method_type|
method_type.sub(subst)
definition.methods[method].defs.map do |type_def|
type_def.update(
type: type_def.type.sub(subst)
)
end
when Types::ClassSingleton
definition.methods[method].method_types
definition.methods[method].defs
else
raise
end
end

def method_types(method)
method_defs(method).map(&:type)
end

def allows_error(*errors)
yield
rescue *errors => exn
Expand Down
4 changes: 2 additions & 2 deletions sig/test/type_check.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ module RBS
#
# Returns an array with detected errors.
#
def method_call: (Symbol, MethodType, CallTrace, errors: Array[Errors::t]) -> Array[Errors::t]
def method_call: (Symbol, MethodType, CallTrace, errors: Array[Errors::t], ?annotations: Array[AST::Annotation]) -> Array[Errors::t]

# Test if given `value` is compatible to type
#
# Returns `true` if the value has the type.
#
#
def value: (untyped value, Types::t) -> bool
end
end
Expand Down
2 changes: 2 additions & 0 deletions sig/unit_test/spy.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module RBS
| [T, S] (untyped object, Symbol method_name) { (WrapSpy[T], T) -> S } -> S

class WrapSpy[T]
NO_RETURN: Object

attr_accessor callback: ^(Test::CallTrace) -> void

attr_reader object: T
Expand Down
2 changes: 2 additions & 0 deletions sig/unit_test/type_assertions.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ module RBS
#
def class_class: () -> Class

def method_defs: (Symbol) -> Array[Definition::Method::TypeDef]

def method_types: (Symbol) -> Array[MethodType]

def allows_error: (*Exception) { () -> void } -> void
Expand Down
4 changes: 4 additions & 0 deletions test/stdlib/Array_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ def test_lshift
def test_aref
assert_send_type "(Integer) -> Integer",
[1,2,3], :[], 0
assert_send_type "(Integer) -> nil",
[1,2,3], :[], 1000
assert_send_type "(Float) -> Integer",
[1,2,3], :[], 0.1
assert_send_type "(ToInt) -> Integer",
Expand Down Expand Up @@ -794,6 +796,8 @@ def test_shift
[1,2,3], :shift
assert_send_type "(ToInt) -> Array[Integer]",
[1,2,3], :shift, ToInt.new(1)
assert_send_type "() -> nil",
[], :shift
end

def test_shuffle
Expand Down
28 changes: 16 additions & 12 deletions test/stdlib/MatchData_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,27 @@ def test_initalize_copy

def test_bytebegin
if_ruby("3.4"...) do
with_backref do |backref|
assert_send_type '(MatchData::capture) -> Integer',
INSTANCE, :bytebegin, backref
assert_send_type '(MatchData::capture) -> Integer',
INSTANCE2, :bytebegin, backref
end
assert_send_type '(String) -> Integer',
INSTANCE, :bytebegin, 'a'
assert_send_type '(Symbol) -> Integer',
INSTANCE, :bytebegin, :a
assert_send_type '(Integer) -> Integer',
INSTANCE2, :bytebegin, 0
assert_send_type '(Integer) -> nil',
INSTANCE2, :bytebegin, 1
end
end

def test_byteend
if_ruby("3.4"...) do
with_backref do |backref|
assert_send_type '(MatchData::capture) -> Integer',
INSTANCE, :byteend, backref
assert_send_type '(MatchData::capture) -> Integer',
INSTANCE2, :byteend, backref
end
assert_send_type '(String) -> Integer',
INSTANCE, :byteend, 'a'
assert_send_type '(Symbol) -> Integer',
INSTANCE, :byteend, :a
assert_send_type '(Integer) -> Integer',
INSTANCE2, :byteend, 0
assert_send_type '(Integer) -> nil',
INSTANCE2, :byteend, 1
end
end

Expand Down
4 changes: 2 additions & 2 deletions test/stdlib/ObjectSpace_WeakKeyMap_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ def test_getkey
map["foo"] = 123

assert_send_type(
"(::Integer) -> ::String",
map, :getkey, 123
"(::String) -> ::String",
map, :getkey, "foo"
)
assert_send_type(
"(::String) -> nil",
Expand Down
17 changes: 6 additions & 11 deletions test/stdlib/Symbol_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,17 +134,12 @@ def test_casecmp
def test_casecmp?
%i[a A s S z Z].each do |other|
assert_send_type '(Symbol) -> bool',
:s, :casecmp?, other
end

# invalid encoding
assert_send_type '(Symbol) -> nil',
'\u{e4 f6 fc}'.encode('ISO-8859-1').to_sym, :casecmp?, :'\u{c4 d6 dc}'

with_untyped.and :sym do |other|
assert_send_type '(untyped) -> bool?',
:a, :casecmp?, other
:s, :casecmp?, other
end
assert_send_type '(String) -> nil',
:abc, :casecmp?, "abc"
assert_send_type '(Integer) -> nil',
:abc, :casecmp?, 1
end

def test_downcase
Expand Down Expand Up @@ -177,7 +172,7 @@ def test_encoding
def test_end_with?
assert_send_type '() -> bool',
:a, :end_with?

with_string 'a' do |string_a|
assert_send_type '(string) -> true',
:a, :end_with?, string_a
Expand Down

0 comments on commit 964066e

Please sign in to comment.