- Matz's plan for Ruby 3 includes Ruby Signature (RBS), Type inference for non-annotated code (Type Profiler), and type checking for annotated code.
- RBS is the standard language for describing Ruby program types and will ship with Ruby 3. Type Profiler infers types for non-annotated Ruby code by running code in a "type-level".
- A demonstration of Type Profiler showed it generating prototypes of signatures for the ao.rb and optcarrot codebases in under a minute, though improvements are still needed to handle more language features.
Block diagram reduction techniques in control systems.ppt
A Static Type Analyzer of Untyped Ruby Code for Ruby 3
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
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. 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
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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. Acknowledgement
• Hideki Miura
• Ruby committers: matz, akr, ko1, soutaro
• Katsuhiro Ueno & Eijiro Sumii
• Stripe team & Shopify team & Jeff Foster
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