Share
Embed
Transcript
40 slides
-
A Static Type Analyzer of Untyped Ruby Code for Ruby
3 Yusuke Endoh RubyConf 2019 19th Nov. 2019 1 -
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 -
PR: Cookpad Inc. • Mission: "Make Everyday Cooking Fun" •
cookpad.com: A recipe sharing service • Monthly Average Users: 93 million 3 -
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) -
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 -
This Talk: Types in Ruby 3 •➔ Matz's plan for
Ruby 3 Types • Ruby Signature • Type Profiler 6 -
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? -
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 -
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 -
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 -
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 -
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 -
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 -
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 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 -
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 -
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 -
This Talk: Ruby 3 types • Matz's plan for Ruby
3 Types • Ruby Signature •➔Type Profiler • Demo • Approach • Problems 18 -
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 -
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 -
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 -
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 -
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) -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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) -> ... -
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 -
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!!! -
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 -
Acknowledgement • Hideki Miura • Ruby committers: matz, akr, ko1,
soutaro • Katsuhiro Ueno & Eijiro Sumii • Stripe team & Shopify team & Jeff Foster 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