yuyakawai0411 / introduce_rbs_to_ruby

rubyにrbsとsteepを導入するための資料です
0 stars 0 forks source link

steepがHash型のkeyまで検査してくれない #10

Open yuyakawai0411 opened 11 months ago

yuyakawai0411 commented 11 months ago

事象

$steep checkを以下の対象のrubyコードに対して行ったところ、以下のようにレコード型にHash[::Symbol, ::String]型の値を入れようとして、型エラーになる。

lib/intersection.rb:17:9: [error] Cannot pass a value of type `::Hash[::Symbol, ::String]` as an argument of type `::name_hash`
│   ::Hash[::Symbol, ::String] <: ::name_hash
│     ::Hash[::Symbol, ::String] <: { :name => ::String }
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└ say_name(sample_1)

対象のrubyコード

sample_1 = {name: 'Pochi'}
sample_2 = {age: 10}
sample_3 = {name: 'Pochi', age: 10 }

def say_name(hash)
  puts "#{hash}です!!"
end

def say_age(hash)
  puts "#{hash}歳です!!"
end

def say_name_and_age(hash)
  puts "#{hash}です!! #{hash}歳です!!"
end

say_name(sample_1)
say_age(sample_2)
say_name_and_age(sample_3)

対象のrbsコード

type name_hash = { name: String }
type age_hash = { age: Integer }
type name_and_age_hash = name_hash & age_hash

class Object
  private
  def say_name: (name_hash) -> nil
  def say_age: (age_hash) -> nil
  def say_name_and_age: (name_and_age_hash) -> nil
end

考察

固定のキーを持つHashはレコード型として表現できるため、rbsの型定義ではなく、steepの型解析の部分に原因がありそう https://github.com/ruby/rbs/blob/master/docs/syntax.md#record-type

yuyakawai0411 commented 11 months ago

補足

以下の方法でsay_nameに正しい型が付与されていることを確認した。 ちなみに、irbのlodarで.rbsファイルを読み込もうとするとエラーになる。iirbがrubyファイルしかloadできないため

root@65ef29ea6f92:/myapp/ruby# irb
irb(main):001:0> require "rbs"
=> true
irb(main):002:0> loader = RBS::EnvironmentLoader.new()
=>
#<RBS::EnvironmentLoader:0x000055c4e5466ec8
...
irb(main):003:0> loader.add(path: Pathname("sig"))
=> [#<Pathname:sig>]
irb(main):004:0> environment = RBS::Environment.from_loader(loader).resolve_type_names
=> #<RBS::Environment @declarations=(929 items) @class_decls=(316 items) @class_alias_decls=(0 items) @interface_decls=(29 items) @type_alias_decls=(22 items) @constant_decls=(569 items) @glo...
irb(main):005:0> builder = RBS::DefinitionBuilder.new(env: environment)
=>
#<RBS::DefinitionBuilder:0x000055c4e65f65f8
...
irb(main):006:0> object = RBS::TypeName.new(name: :Object, namespace: RBS::Namespace.root)
=> #<RBS::TypeName:0x000055c4e6827480 @kind=:class, @name=:Object, @namespace=#<RBS::Namespace:0x000055c4e4fdd5c8 @absolute=true, @path=[]>>
irb(main):007:0> instance = builder.build_instance(object)
=> #<RBS::Definition:0x000055c4e69ee250>
irb(main):008:0> puts instance.methods[:say_name].method_types.join("\n")
(::name_hash) -> nil
=> nil