ãã¹ããå®è¡ãã¦Rubyã®åæ å ±ãéãããã¤ãä½ã£ã
ã¤ã³ãããã¯ã·ã§ã³
ããã¹ããèµ°ããã¦åæ å ±ãåéããã°ããããããªãï¼ããã®ã¢ã¤ãã¢èªä½ã¯è©±é¡ã«ä¸ãããã¨ãå¤ãã£ããã¨æããã¾ããã観測ç¯å²ã§ã¯åä¾ããªãããã«è¦ãã¾ããããã§ãå®éã«ä½ã£ã¦ããè¦ããä¸çãããã¨æãåããã®ãå®è£ ãã¦ã¿ã¾ããã
Orthoses::Trace
orthosesã¯RBSãçæããããã®æ©è½ãä½ããã¬ã¼ã ã¯ã¼ã¯ã§ããã®æ©è½ã®ä¸ã¤ã¨ãã¦Orthoses::Trace
ã¨ããããã«ã¦ã§ã¢ãå®è£
ãã¾ããã
ä¾
ä¾é¡ã¨ãã¦ãrack-testã¨ããgemã®RBSãçæãããã¨ãã¾ãã
ãã®å ´åã®çæã³ã¼ããOrthoses::Trace
ã使ã£ã¦ä»¥ä¸ã®ããã«æºåãã¾ãã
require 'pathname' require 'orthoses' require 'fileutils' include FileUtils out = Pathname('out') out.rmtree rescue nil Orthoses.logger.level = :warn Orthoses::Builder.new do use Orthoses::CreateFileByName, base_dir: out.to_s use Orthoses::Filter do |name, content| name.start_with?("Rack::Test") end use Orthoses::Trace, patterns: ['Rack::Test*'] run ->(){ cd "src" do load "spec/all.rb" Minitest.run # avoid to run on at_exit module ::Minitest def self.run args = [] 0 end end end } end.call
ãã®ã³ã¼ããå®è¡ããã¨ãrack-testã®ãªãã¸ããªå ã®ãã¹ãã³ã¼ããèµ°ãããRBSãçæãããã¨ãã§ãã¾ãã
Orthoses::CreateFileByName
ããã«ã¦ã§ã¢ã®åãã«ãã£ã¦classæ¯ã«ãã¡ã¤ã«ãã§ãã¾ãã
ãã®ä¸é¨ãè¦ã¦ã¿ã¾ããããRack::Test::UploadedFile
ã®å ´åã¯ä»¥ä¸ã®ãããªåºåãå¾ããã¨ãã§ãã¾ãã
class Rack::Test::UploadedFile attr_reader tempfile: Tempfile? attr_reader original_filename: String attr_accessor content_type: String def self.finalize: (Tempfile file) -> Proc private def initialize_from_file_path: (String path) -> nil private def initialize: (String content, ?String content_type, ?bool binary, ?original_filename: nil) -> void | (StringIO content, ?String content_type, ?bool binary, ?original_filename: String) -> void | (String content, ?String content_type, ?bool binary, ?original_filename: String) -> void | (StringIO content, ?String content_type, ?bool binary, ?original_filename: nil) -> void private def respond_to_missing?: (Symbol method_name, ?bool include_private) -> bool def method_missing: (Symbol method_name, *Array[untyped] args) ?{ (*untyped) -> untyped } -> (Integer | String) | (Symbol method_name, *Array[Encoding] args) ?{ (*untyped) -> untyped } -> File def append_to: (String buffer) -> nil def self.actually_finalize: (Tempfile file) -> bool private def initialize_from_stringio: (StringIO stringio) -> StringIO def path: () -> String end
ãã¹ãã³ã¼ããèµ°ããã¦ãããã¨ããã£ã½ãRBSãå¾ããã¨ãã§ãã¾ããã
å®è£ æ¹æ³
TracePointã使ãã¾ãããåºæ¬çã«ã¯TracePointã®call
ã¤ãã³ãã§å¼æ°ã®åããreturn
ã¤ãã³ãã§è¿ãå¤ã®åãããããããã¹ãå®è¡æã«éãã¦ããã¨ããçºæ³ã§ãã
ã³ã¼ãä¾ã§èª¬æãã¾ãã
def foo(a) a.id end
ã¿ãããªã³ã¼ãããã£ãã¨ãã«ããã®ã¾ã¾ã§ã¯a.id
ãä½ãè¿ãã®ããããã¾ããã
ããã§ãå®éã«ã³ã¼ããå®è¡ããfoo
ãå¼ã°ããã¨ãã®a
ã®å¤ãè¦ã¦å¼æ°ã®åããè¿ãå¤ãè¦ã¦è¿ãå¤ã®åãfoo
ã®åã¨ãã¦èç©ãã¦ããã¾ãã
çµæã¨ãã¦
- å¼æ°aã¯
Record
å, è¿ãå¤ã¯Integer
ã ã£ãã - å¼æ°aã¯
Record
å, è¿ãå¤ã¯nil
ã ã£ãã
ã¨ããæ å ±ãéãããã¨ãã§ãã¾ãã
ãããã®æ å ±ãçµ±åãã¦
def foo: (Record) -> Integer?
ã¨ããRBSã«è½ã¨ãè¾¼ããã¨ããã®ãåºæ¬çãªçºæ³ã§ãã ã³ã¼ãã®å®è¡ãå¿ è¦ã§ã¯ããã¾ãããã³ã¼ããå®è¡ããæé«ã®ãµã³ãã«ã¨ãã¦ãã¹ãã³ã¼ãã使ç¨ããã°ãæ¢åã®è³ç£ãæµç¨ãã¤ã¤0ãããããé¥ãã«å¹ççã«RBSãè¨è¿°ãããã¨ãæå¾ ã§ãã¾ãã
æ¢åã®ææ³
éçãªåãã¡ã¤ã«ãèªåçæããæ¢åã®ææ³ã¨ãã¦ã¯ä»¥ä¸ã®åä¾ãããã¾ãã ãããã®åä¾ã¨ä»åã®ææ³ãæ¯è¼ããã©ã®ãããªã¡ãªãããããã®ããèãã¾ãã
rbs prototype
ãã®åã®éããRBSã®ãããã¿ã¤ããä½æããããã®ã³ãã³ãã§ãã éç解æã¨åç解æã®ä¸¡æ¹ããããã³ãã³ãã²ã¨ã¤ã§çæã§ããã®ã§ãRBSã®ãããã¿ã¤ãçæã¨ãã¦æãæåãªæ¹æ³ã ã¨æããã¾ãã
typeprof
éç解æã«ãã£ã¦ã§ããã ãåæ å ±ãåéããææ³ã§ããããªã精度ãé«ãããããªãã¨ã¾ã§åããã®ãï¼ãã¨é©ãã¾ãã
rbs-dynamic
RubyKaigi2022ã§çºè¡¨ããããã¼ã«ã§ãã³ã¼ããå®è¡ãã¦åæ å ±ãå¾ããã®ã§ãã ä»åå®æ½ããææ³ã«ããªãè¿ãã®ã§ãããä»åã®ããã«æ¢åã®ãã¹ãã³ã¼ããã¾ããã¨åãç¨éã§ã¯ã¨ã©ã¼ãçºçãåãããªãã£ãã®ã§ä»åã¯ä½¿ç¨ãã¦ãã¾ããã
æ¯è¼
rbs prototype rb
ã½ã¼ã¹ã³ã¼ãã«å¯¾ãã¦å®è¡ãã¾ããç°¡ç¥åã®ããã³ã¡ã³ãã¯é¤ãã¾ãã
$ rbs --version rbs 3.0.4 $ rbs prototype rb src/lib/rack/test/uploaded_file.rb | grep -v '#' module Rack module Test class UploadedFile attr_reader original_filename: untyped attr_reader tempfile: untyped attr_accessor content_type: untyped def initialize: (untyped content, ?::String content_type, ?bool binary, ?original_filename: untyped?) -> void def path: () -> untyped alias local_path path def method_missing: (untyped method_name, *untyped args) { () -> untyped } -> untyped def append_to: (untyped buffer) -> nil def respond_to_missing?: (untyped method_name, ?bool include_private) -> untyped def self.finalize: (untyped file) -> untyped def self.actually_finalize: (untyped file) -> untyped private def initialize_from_stringio: (untyped stringio) -> untyped def initialize_from_file_path: (untyped path) -> untyped end end end
rbs prototype runtime
$ rbs prototype runtime -r rack/test/uploaded_file 'Rack::Test::UploadedFile'
rbs prototype rbã¨ã»ã¼å¤ãããªãçµæã¨ãªã£ãã®ã§å²æ
typeprof
$ typeprof --version typeprof 0.21.7 $ typeprof src/lib/rack/test.rb src/spec/rack/test/uploaded_file_spec.rb # ...snip... class UploadedFile attr_reader original_filename: String? attr_reader tempfile: String | StringIO attr_accessor content_type: String def initialize: (String | StringIO content, ?String content_type, ?bool binary, ?original_filename: String?) -> void def path: -> untyped alias local_path path def method_missing: (:must_respond_to | :pos | :read method_name, *Symbol args) -> untyped def append_to: (untyped buffer) -> nil def respond_to_missing?: (untyped method_name, ?false include_private) -> true def self.finalize: (String | StringIO file) -> ^-> untyped def self.actually_finalize: (untyped file) -> untyped private def initialize_from_stringio: (String | StringIO stringio) -> (String | StringIO) def initialize_from_file_path: (String | StringIO path) -> void end # ...snip...
æ¯è¼èå¯
åºåã¯ä¸é¨ãã示ãã¦ãã¾ããããå ¨ä½çãªæ¯è¼ã¨ãã¦ä»¥ä¸ã®ããã«ãªãã¾ããã
Tool | ã¡ãªãã | ãã¡ãªãã | åè |
---|---|---|---|
rbs prototype | å®è¡ãé常ã«æ軽ã ã£ã | ã»ã¨ãã©ãuntypedã ã£ã | ãªã |
typeprof | å®è¡ãæ軽ããã¤ããç¨åº¦åããããã¤ã³ã¹ã¿ã³ã¹å¤æ°ã対å¿ãã¦ããã | untypedã¯ãã | rbs collectionã§åæ å ±ã追å ã§ããå¯è½æ§ããã |
Orthoses::Trace | åæ å ±ãå¤ã | å®è¡ã®ããã«ã³ã¼ããæ¸ãå¿ è¦ããã | rbs collectionã§åæ å ±ã追å ã§ããå¯è½æ§ããã |
æ軽ãã¨æ£ç¢ºæ§ã«ããç¨åº¦ã®ãã¬ã¼ãã»ãªãã®é¢ä¿ãããããã«è¦ãã¾ãã
ããå°ãç´°ããæ¯è¼ãã¦ã¿ã¾ãããã
å®è¡ã®æ軽ã
å§åçã«rbs prototypeãtypeprofãæ軽ã§ããOrthoses::Trace
ã§ã¯ã¡ãã£ã¨ããã¹ã¯ãªããã¬ãã«ã®ã³ã¼ããæ¸ãå¿
è¦ãããæ軽ã¨ã¯è¨ãã¾ããã
åã®æ£ç¢ºæ§
æ¹æ³ã«ãã£ã¦èæ ®ããã¦ããåæ å ±ã«å·®ã¯ããã¾ãããã©ã®ææ³ã§ãæçµçãªåæ å ±ã¯ããã°ã©ãã¼ã®çæ³ã®ãã®ã«ãªãã¯ãã§ãããã£ã¦ãããã®ææ³ã§ãããç¨åº¦ã®æç´ãã¯å¿ è¦ããã§ãã
対å¿ç¯å²
typeprofã¯å®æ°ãã¤ã³ã¹ã¿ã³ã¹å¤æ°ã対å¿ãã¦ããã®ã«å¯¾ãã¦ãOrthoses::Trace
ã§ã¯TracePoint軸ãªã®ã§ããã¯ãè¨å®ã§ã対å¿ã§ãã¾ãããtypeprofãããã
ã¾ããOrthoses::Trace
ã§ã¯ãå¼ã°ãã¦ããªãã¡ã½ããã¯æ
å ±ããªãã®ã§åçæã§ãã¾ãããtypeprofã¯å¼ã°ãã¦ãªãã¦ãé¡æ¨ã§ãã¾ããtypeprofãããã
ãã¹ããã¼ã«ãã¨ã®å¯¾å¿
Orthoses::Trace
ã§ã¯ã³ã¼ããæ¸ããã¨ã§ãã¹ããèµ°ãããåæ
å ±ãå¾ããã¨ãã§ãããã¨ããããã¾ããã
ãã¹ããã¼ã«ã¯æ§ã
ã«ãããminitestãtest-unitãrspecãªã©ãæåã§ããã³ã¼ããæ¸ã以ä¸ãããããã®ãã¼ã«ã«å¯¾å¿ããã³ã¼ããæ¸ãå¿
è¦ãããã¾ãã
ããã§ã¯Orthoses::Trace
ã使ã£ãå ´åã®ãã¹ããã¼ã«æ¯ã®ã³ã¼ãã®æ¸ãæ¹ã示ãã¦ããã¾ãã
ã¾ããã¹ãã³ã¼ããå«ãã½ã¼ã¹ã³ã¼ãå
¨ä½ãå¿
è¦ãªã®ã§ãã½ã¼ã¹ã³ã¼ããgit clone
çã§ãã¦ã³ãã¼ããã¦ããã¾ãã
以å¾èª¬æã®ããããã®ã½ã¼ã¹ã³ã¼ãç½®ãå ´ã®ãã£ã¬ã¯ããªã¼åãä»®ã«src
ã¨ãã¾ãã
minitest
æåã«ç¤ºããrack-testãminitestã使ã£ã¦ãã¾ãã
minitestã使ã£ã¦ããå ´åãautorunã使ã£ã¦ããªããã°load "test/run.rb"
ã¿ãããªæãã§ãã¹ãã³ã¼ããèªã¿ãMinitest.run
ã§ãã¹ããèµ°ããããã¨ãã§ãã¾ãããããã大æµã¯autorunã使ã£ã¦ããã®ã§å·¥å¤«ãå¿
è¦ã§ãã
autorunã¯at_exit
ã§å®è¡ãããã®ã§ããã®ã¿ã¤ãã³ã°ã ã¨orthosesã§è§£æããProcã®å¤ã«ãªã£ã¦ãã¾ãã¾ãã
ãªã®ã§Procå
ã§ãã¹ããå®è¡ãã¤ã¤ãautorunãæ¶ãããã«ã¡ã½ãããã¾ããã¨ä¸æ¸ããã¦ãã¾ããMinitest.run
ã¯exit codeãè¿ãã¤ã³ã¿ã¼ãã§ã¼ã¹ãªã®ã§0
ãè¿ãã®ããã½ã§ãã
test-unit
å¤åãããªæãã ã¨æãã¾ãã
Orthoses::Builder.new do use Orthoses::Trace, patterns: ['Foo*'] run ->(){ cd "src" do load "test/foo_test.rb" Test::Unit::AutoRunner.run class ::Test::Unit::AutoRunner def self.run(*, **) 0 end end end } end
rspec
ããã¾ãrspecã使ã£ãã©ã¤ãã©ãªã¼ãè¦ã¤ããããªãã£ãã®ã§ããããããã以ä¸ã§åãã¨æãã¾ãã ãã ãããã¹ããæåããªãã¨exitã§å¼·å¶çµäºãã¦ãã¾ãã®ã§ããããã¹ãã失æãã¦ããã¨ãããã¨ã¯åã¯ééã£ã¦ããå¯è½æ§ãé«ãã®ã§ããã§ããã®ããã
require "rspec/core" Orthoses::Builder.new do use Orthoses::Trace, patterns: ['Foo*'] run ->(){ cd "src" do load "spec/foo.rb" RSpec::Core::Runner.invoke end } end
ããã©ã
ãã¹ãã©ã¤ãã©ãªã¼ã«åããã¦æè»ã«å¯¾å¿ã§ããåãèªåã§ã³ã¼ããæ¸ããªããã°ãããªãã®ãããã©ãã®ã§ãããå°ããµãã¼ãæ¹æ³ãèãããã¨ããã§ããããã ããããªãããªãã¸ããªæ¯ã«ãã¹ãã®èµ°ããæ¹ã¯åå·®ä¸å¥ã§ãgemã®ä¾åé¢ä¿ãããããç°ãªã£ã¦ããã®ã§çµ±ä¸ãããæ¹æ³ ã¯é£ããã¨èãã¦ãã¾ãã
æãã¨ãã
TracePoint#enable(target: )åé¡
attr_accessor
ç³»ã®ã¡ã½ãããRubyVM::InstructionSequence.of
ã«å¯¾å¿ãã¦ãããã¨ããå°ãã³ã¼ããç°¡åã«ãªãã®ã§ããã¬ã¢ã±ã¼ã¹ããâ¦â¦ã
å®è£ ã®è©³ç´°
å®è£ é¢ã¯ããªã端æãã¾ããããå®ã¯é¢ç½ã工夫ãçãã ãããã§ããTracePointã«èå³ããã人ã¯è¦ãã¦ã¿ã¦ãã ããã
typeprofããã
ã¨ãããã¨ã§Orthoseså ã§typeprofãå¼ã¶ãã¨ãã§ããæ¡å¼µãä½ãã¾ããã
https://github.com/ksss/orthoses-typeprof
ããã¾ã§ã«Orthoseså ã§èç©ããæ å ±ãtypeprofã«æ¸¡ãã¦åã®åèå¤ã«ãããã¨ãã§ãã¾ãã
ã¡ãªã¿ã«ä»åã®åºåçµæã¯ä»¥ä¸ã«ã¾ã¨ããã®ã§æ¯è¼ãã¦ã¿ã¦ãã ããã
Orthoses::Trace: https://github.com/ksss/orthoses/tree/main/examples/rack-test/out/rack typeprof: https://github.com/ksss/orthoses-typeprof/tree/main/examples/rack-test/out/rack
ææ³
ããã§ã©ã¤ãã©ãªã¼ã®RBS追å ãå°ã楽ã«ãªãããâ¦â¦ï¼ã©ãã ãããtypeprofã§ååãªã®ãããããªãâ¦â¦ã
ã¨ããããä½ã£ã¦ã¿ãäºã¯ã§ããã®ã§ãä»å¾ã¯ä½¿ã£ã¦ã¿ã¦ä¾¿å©ãã©ãã確ããããã§ããããâ¦â¦ã
ããããä½ããããããããã¨ããã£ã¦ããã®ã¨ãã«æãã¤ããææ³ãªã®ã§ãããæãã¤ãã¦å®è£ ãã¦ããã°ãæ¸ãã®ã«5ã¶æãããããã£ã¦ãã¾ãæ¬æ¥ã®ç®çãè¦å¤±ãã¾ããããå¾ãããããããã§ã
æãã¤ããã¨ãã
ãããããããªã³ã¼ãæãã¤ãã¦è©¦ãã¦ã¿ããããããã§è³æ±åºã¦ãããããªæé
— ksss (@_ksss_) 2022å¹´10æ30æ¥