Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

A Static Type Analyzer of Untyped Ruby Code for Ruby 3

140 views

Published on

@ RubyConf 2019

Published in: Engineering
  • Be the first to comment

  • Be the first to like this

A Static Type Analyzer of Untyped Ruby Code for Ruby 3

  1. 1. A Static Type Analyzer of Untyped Ruby Code for Ruby 3 Yusuke Endoh RubyConf 2019 19th Nov. 2019 1
  2. 2. Yusuke Endoh (@mametter) • A Ruby committer: • Keyword argument design and implementation (2.0) • Optcarrot: a benchmark for Ruby 3x3 • Ruby 2.0 release manager • Working at Cookpad Inc. w/ @ko1 2
  3. 3. PR: Cookpad Inc. • Mission: "Make Everyday Cooking Fun" • cookpad.com: A recipe sharing service • Monthly Average Users: 93 million 3
  4. 4. PR: Cookpad Inc. 4 We're hiring! HQ is in Bristol, UK Aim to be No.1 in 100 countries (Now in 30 Languages and 73 Countries)
  5. 5. This Talk: Types in Ruby 3 • Matz's plan for Types in Ruby 3 Multiple type checkers in one blueprint • Ruby Signature The standard type signature format for stdlib and gems • Type Profiler A type analysis for non-annotated Ruby code 5
  6. 6. This Talk: Types in Ruby 3 •➔ Matz's plan for Ruby 3 Types  • Ruby Signature • Type Profiler 6
  7. 7. The Objective of Ruby 3 types Point out possible bugs without execution • To improve development experience (In other words, a few wrong alerts are acceptable) 7 def increment(n) n.timees { } n + "STRING" end increment(42) NoMethodError? TypeError?
  8. 8. Question 🙋 Do you want to write a type annotation? 8 extend T::Sig sig {params(n:Integer).returns(Integer)} def increment(n) n + 1 end increment(42) type annotation (in Sorbet style) source code
  9. 9. Thank you. Just as I expected • In Ruby 3, you can write annotations if you like • You will gain relatively strong type checking • Moreover, in Ruby 3, • You don't have to write annotations manually • You can even check the code with no annotations! (if my project succeeds ☺) 9
  10. 10. Thank you. That was unexpected • In Ruby 3, you can write annotations if you like • You will gain relatively strong type checking • Moreover, in Ruby 3, • You don't have to write annotations manually • You can even check the code with no annotations! (if my project succeeds ☺) 10
  11. 11. Ruby 3 will have three items 1. Ruby Signature (RBS) language 2. Type inference for non-annotated code (Type Profiler) 3. Type checking for annotated code (Sorbet, RDL, Steep, etc.) I explain 1 and 2 in this talk... 11
  12. 12. This Talk: Ruby 3 types • Matz's plan for Ruby 3 Types •➔Ruby Signature • What it is / Examples / What Ruby 3 will ship • Type Profiler 12
  13. 13. Soutaro Matsumoto (@soutaro) • Leads the design and implementation of RBS • https://github.com/ruby/ruby-signature • Develops own static type checker "Steep" • [Correction] No session about Steep this year! • Working for Square 13
  14. 14. 1. Ruby signature language (RBS) • The standard language to describe types of Ruby programs • Different syntax: to keep Ruby code unannotated • Ruby3 will ship with signatures for stdlib and a library for RBS 14 class Inc def increment(n) n + 1 end end inc.rbs inc.rb class Inc def increment: (Integer) -> Integer end separated files
  15. 15. 15 class Array[T] def []: (Integer) -> T | (Integer, Integer) -> Array[T] def first: () -> T? def each: () { (T) -> void } -> Array[T] include Enumerable[T, Array[T]] end Generics Optional Types Overloading Block Mixin interface _Duck def quack: () -> void end Duck typing
  16. 16. Using RBS • For type-checking • Static type checking needs signatures of libraries • Steep uses RBS to define the signature of your Ruby applications • For documentation • RBS explains the API of a gem 16
  17. 17. Ship your gems with RBS • Scaffold from Ruby code / Sorbet RBI • Generate using Type Profiler • [WIP] A tool to test RBS definitions by dynamic type checking 17 $ rbs scaffold rb # from unannotated Ruby code $ rbs scaffold rbi # from Sorbet annotated code
  18. 18. This Talk: Ruby 3 types • Matz's plan for Ruby 3 Types • Ruby Signature •➔Type Profiler • Demo • Approach • Problems 18
  19. 19. Type Profiler A kind of "type inference" for non-annotated code def increment(n) n + 1 end increment(42) 19 github.com/mame/ruby-type-profiler infer def increment: (Integer) -> Integer
  20. 20. Type Profiler Serves as a (weak) type checker def increment(n) n.timees { } n + "STRING" end increment(42) 20 [error] undefined method: Integer#timees check [error] failed to resolve overload: Integer#+(String) check
  21. 21. Demo • ao.rb • optcarrot 21
  22. 22. Demo: ao.rb • A 3D rendering program • ~300 lines of code • Written by Hideki Miura • Original version (js) was created by Syoyo Fujita https://code.google.com/archive/p/aobench/ • Analysis time < 1sec. 22
  23. 23. Demo: ao.rb 23 class Vec @x : Complex | Float | any @y : Complex | Float | any @z : Complex | Float | any initialize : (Complex, Complex, Complex) -> Complex | (Complex, Complex, Float) -> Float … vnormalize : () -> Vec vlength : () -> any vdot : (Vec) -> (Complex | Float) x : () -> (Complex | Float) x= : (Complex) -> Complex | (Float) -> Float A class signature for Vec (3D vector) vector operations "any" should be fixed manually Three instance variables
  24. 24. Demo: ao.rb 24 class Scene @spheres : [] @plane : Plane initialize : () -> Plane render : (Integer, Integer, Integer) -> Integer ambient_occlusion : (Isect) -> Vec end class Plane @p : Vec @n : Vec initialize : (Vec, Vec) -> Vec intersect : (Ray, Isect) -> (NilClass | Vec)
  25. 25. Demo: ao.rb 25 class Ray @org : Vec @dir : Vec initialize : (Vec, Vec) -> Vec dir : () -> Vec org : () -> Vec end class Isect @t : Complex | Float | any @hit : Boolean @pl : Vec @n : Vec
  26. 26. Demo: ao.rb • TP generates a good prototype of signatures • Can be used as a signature with some fixes • May be also useful for program understanding • There are some wrong / incomplete guesses • Due to lack of knowledge of methods, analysis limitation, etc. • Some of them can be fixed by TP improvement 26
  27. 27. Demo: optcarrot • 8-bit machine emulator • Circuit emulation program • 5000 LOC • Author: me ☺ • Analysis time ~ 20 sec. 27 https://eregon.me/blog/2016/11/28/optcarrot.html
  28. 28. Demo: optcarrot 28 class Optcarrot::NES @conf : Optcarrot::Config @video : any @audio : any @input : any @cpu : Optcarrot::CPU @apu : Optcarrot::APU @ppu : Optcarrot::PPU initialize : () -> None end Three circuit modules: CPU, APU (Audio), PPU (Graphics) Failed to detect other methods
  29. 29. Demo: optcarrot 29 class Optcarrot::APU ... @pulse_1 : Optcarrot::APU::Pulse @pulse_0 : Optcarrot::APU::Pulse @triangle : Optcarrot::APU::Triangle @noise : Optcarrot::APU::Noise ... end Audio processor unit has four wave generators
  30. 30. Demo: optcarrot • TP just showed shallow analysis result • Due to lack of knowledge about many builtin classes/methods (such as Fiber, etc.) • Still, it looks useful to create a prototype of signatures • TP is never perfect, but I believe it is promising 30
  31. 31. A Key Idea of Type Profiler Runs a Ruby code in "type-level" Traditional interpreter def foo(n) n.to_s end foo(42) Calls w/ 42 Returns "42" Type Profiler def foo(n) n.to_s end foo(42) Calls w/ Integer Returns String Object#foo :: (Integer) -> String 31
  32. 32. Type Profiler and Branch "Forks" the execution def foo(n) if n < 10 n else "error" end end foo(42) Fork! Now here We cannot tell if n<10 or not Object#foo :: (Integer) -> (Integer | String) 32 Returns String Returns Integer
  33. 33. Difficulties of Type Profiler • Requires a starting point • Needs to be integrated with a test framework • Cannot analyze some language features • Is still very preliminary 34
  34. 34. A staring point is required • TP cannot infer untested methods 35 def inc(n) n end inc(42) infer def inc: (Integer) -> Integer A test code def inc(n) n end Untested infer def inc: (any) -> any
  35. 35. Test framework integration is needed • Some test may lead to a wrong guess 36 def foo(n) n+1 end assert_raise { foo("s") } infer def foo: (String) -> any A test excepts an exception def bar(n) n end foo(MockObject.new) A test passes a mock object infer def bar: (MockObject) -> ...
  36. 36. Difficult features to analyze • Typically, TP cannot trace Object#send • Singleton methods, Object#eval, binding, etc... • You need manually write RBS in this case 37 def inc(n) n end send("inc".to_sym, 42) infer def inc: (any) -> any The Symbol cannot be determined in type-level
  37. 37. TP is still Preliminary • Designing TP is harder than MRI-compatible normal interpreter • Many features are not supported yet • Notable unsupported-yet features: Module, and Exception • The analysis performance must be improved 38 It is developed in one person-year 😖 Help, advice, and contribution are welcome!!!
  38. 38. Related Work • mruby-meta-circular (Hideki Miura) • Type Profiler has been inspired by it • Type Analysis for JavaScript (S. H. Jensen, et al.) • RDL infer (Jeff Foster et al.) • An alternative approach to infer types of non-annotated Ruby code • Based on traditional type inference with some heuristics 39
  39. 39. Acknowledgement • Hideki Miura • Ruby committers: matz, akr, ko1, soutaro • Katsuhiro Ueno & Eijiro Sumii • Stripe team & Shopify team & Jeff Foster 40
  40. 40. Conclusion • Explained Matz's plan for Ruby 3 static analysis • Introduced Type Profiler • A type analyzer for Ruby 3 applicable to a non-annotated Ruby code • Based on abstract interpretation technique • Little change for Ruby programming experience • Any comments and/or contribution are welcome! • https://github.com/mame/ruby-type-profiler 41

×