diff --git a/lib/steep/interface/builder.rb b/lib/steep/interface/builder.rb index 97869fa7e..82e6088c7 100644 --- a/lib/steep/interface/builder.rb +++ b/lib/steep/interface/builder.rb @@ -50,7 +50,7 @@ def validate_fvs(name, type) end def upper_bound(a) - variable_bounds.fetch(a, nil) + variable_bounds.fetch(a, AST::Builtin::Object.instance_type) end end diff --git a/lib/steep/interface/type_param.rb b/lib/steep/interface/type_param.rb index ad9a41391..e848f01a5 100644 --- a/lib/steep/interface/type_param.rb +++ b/lib/steep/interface/type_param.rb @@ -1,6 +1,8 @@ module Steep module Interface class TypeParam + IMPLICIT_UPPER_BOUND = AST::Builtin.optional(AST::Builtin::Object.instance_type) + attr_reader :name attr_reader :upper_bound attr_reader :variance diff --git a/lib/steep/subtyping/check.rb b/lib/steep/subtyping/check.rb index 21e0a31f7..7fdc8b0da 100644 --- a/lib/steep/subtyping/check.rb +++ b/lib/steep/subtyping/check.rb @@ -59,7 +59,7 @@ def push_variable_bounds(params) def variable_upper_bound(name) @bounds.reverse_each do |hash| if hash.key?(name) - return hash[name] + return hash.fetch(name) end end @@ -334,17 +334,14 @@ def check_type0(relation) end when relation.super_type.is_a?(AST::Types::Var) && constraints.unknown?(relation.super_type.name) - if ub = variable_upper_bound(relation.super_type.name) - Expand(relation) do - check_type(Relation.new(sub_type: relation.sub_type, super_type: ub)) - end.tap do |result| - if result.success? - constraints.add(relation.super_type.name, sub_type: relation.sub_type) - end + ub = variable_upper_bound(relation.super_type.name) || Interface::TypeParam::IMPLICIT_UPPER_BOUND + + Expand(relation) do + check_type(Relation.new(sub_type: relation.sub_type, super_type: ub)) + end.tap do |result| + if result.success? + constraints.add(relation.super_type.name, sub_type: relation.sub_type) end - else - constraints.add(relation.super_type.name, sub_type: relation.sub_type) - Success(relation) end when relation.sub_type.is_a?(AST::Types::Var) && constraints.unknown?(relation.sub_type.name) @@ -390,8 +387,9 @@ def check_type0(relation) end end - when relation.sub_type.is_a?(AST::Types::Var) && ub = variable_upper_bound(relation.sub_type.name) + when relation.sub_type.is_a?(AST::Types::Var) Expand(relation) do + ub = variable_upper_bound(relation.sub_type.name) || Interface::TypeParam::IMPLICIT_UPPER_BOUND check_type(Relation.new(sub_type: ub, super_type: relation.super_type)) end diff --git a/lib/steep/type_construction.rb b/lib/steep/type_construction.rb index 231d3100e..725bbde45 100644 --- a/lib/steep/type_construction.rb +++ b/lib/steep/type_construction.rb @@ -3846,20 +3846,17 @@ def try_method_type(node, receiver_type:, method_name:, method_overload:, argume type_args.each_with_index do |type, index| param = method_type.type_params[index] - if param.upper_bound - if result = no_subtyping?(sub_type: type.value, super_type: param.upper_bound) - args_ << AST::Builtin.any_type - constr.typing.add_error( - Diagnostic::Ruby::TypeArgumentMismatchError.new( - type_arg: type.value, - type_param: param, - result: result, - location: type.location - ) + upper_bound = param.upper_bound || Interface::TypeParam::IMPLICIT_UPPER_BOUND + if result = no_subtyping?(sub_type: type.value, super_type: upper_bound) + args_ << AST::Builtin.any_type + constr.typing.add_error( + Diagnostic::Ruby::TypeArgumentMismatchError.new( + type_arg: type.value, + type_param: param, + result: result, + location: type.location ) - else - args_ << type.value - end + ) else args_ << type.value end diff --git a/sig/steep/interface/type_param.rbs b/sig/steep/interface/type_param.rbs index bec7e0a57..06fa1f666 100644 --- a/sig/steep/interface/type_param.rbs +++ b/sig/steep/interface/type_param.rbs @@ -1,6 +1,12 @@ module Steep module Interface class TypeParam + # Implicit upper bound given to unqualified generic parameter + # + # It's `Object?` currently. + # + IMPLICIT_UPPER_BOUND: AST::Types::t + type loc = RBS::Location[untyped, untyped] type variance = RBS::AST::TypeParam::variance diff --git a/test/type_check_test.rb b/test/type_check_test.rb index dbceb60ca..795737a9d 100644 --- a/test/type_check_test.rb +++ b/test/type_check_test.rb @@ -2186,4 +2186,34 @@ def foo(x) YAML ) end + + def test_generics_upperbound_implicitly_object + run_type_check_test( + signatures: { + "a.rbs" => <<~RBS + class Foo + def foo: [X] (X) -> void + end + RBS + }, + code: { + "a.rb" => <<~RUBY + class Foo + def foo(x) + x.nil? + end + end + + foo = Foo.new + foo.foo("") + foo.foo(NilClass.new) + RUBY + }, + expectations: <<~YAML + --- + - file: a.rb + diagnostics: [] + YAML + ) + end end