Slide 1

Slide 1 text

A Static Type Analyzer of Untyped Ruby Code for Ruby 3 Yusuke Endoh RubyConf 2019 19th Nov. 2019 1

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

PR: Cookpad Inc. • Mission: "Make Everyday Cooking Fun" • cookpad.com: A recipe sharing service • Monthly Average Users: 93 million 3

Slide 4

Slide 4 text

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)

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

This Talk: Types in Ruby 3 •➔ Matz's plan for Ruby 3 Types  • Ruby Signature • Type Profiler 6

Slide 7

Slide 7 text

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?

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

This Talk: Ruby 3 types • Matz's plan for Ruby 3 Types • Ruby Signature •➔Type Profiler • Demo • Approach • Problems 18

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Demo • ao.rb • optcarrot 21

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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)

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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) -> ...

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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!!!

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Acknowledgement • Hideki Miura • Ruby committers: matz, akr, ko1, soutaro • Katsuhiro Ueno & Eijiro Sumii • Stripe team & Shopify team & Jeff Foster 40

Slide 40

Slide 40 text

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