Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A Static Type Analyzer of Untyped Ruby Code for...

A Static Type Analyzer of Untyped Ruby Code for Ruby 3

RubyConf 2019

Yusuke Endoh

November 20, 2019
Tweet

More Decks by Yusuke Endoh

Other Decks in Programming

Transcript

  1. A Static Type Analyzer of Untyped Ruby Code for Ruby

    3 Yusuke Endoh RubyConf 2019 19th Nov. 2019 1
  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. PR: Cookpad Inc. • Mission: "Make Everyday Cooking Fun" •

    cookpad.com: A recipe sharing service • Monthly Average Users: 93 million 3
  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. 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. This Talk: Types in Ruby 3 •➔ Matz's plan for

    Ruby 3 Types  • Ruby Signature • Type Profiler 6
  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. 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. 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. 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. 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. 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. 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. 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 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. 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. 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. This Talk: Ruby 3 types • Matz's plan for Ruby

    3 Types • Ruby Signature •➔Type Profiler • Demo • Approach • Problems 18
  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. 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. 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
  22. 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
  23. 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)
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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) -> ...
  35. 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
  36. 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!!!
  37. 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
  38. Acknowledgement • Hideki Miura • Ruby committers: matz, akr, ko1,

    soutaro • Katsuhiro Ueno & Eijiro Sumii • Stripe team & Shopify team & Jeff Foster 40
  39. 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