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

Writing Ruby Scripts with TypeProf

Writing Ruby Scripts with TypeProf

Avatar for Yusuke Endoh

Yusuke Endoh

April 17, 2025
Tweet

More Decks by Yusuke Endoh

Other Decks in Technology

Transcript

  1. PR: I wrote a book • 「型システムのしくみ」 "Type Systems Distilled

    with TypeScript" (A Japanese book) • It explains how to write a type checker … for a subset of TypeScript
  2. PR: I wrote a book • Inspired by「型システム入門 (TAPL)」 •

    "Types and Programming Languages" (The most well-known textbook of type systems) • It explains: • Base type system • Subtyping • Recursive types • Generics … in TypeScript I am one of the translators of TAPL (Ja version)
  3. PR: I wrote a book • It uses TypeScript. Why?

    • It sells better than Ruby • First-class functions make it convenient to explain the traditional type systems • I want more contributors for Ruby types • Interested in Steep or Sorbet? Check it out! • Available at the bookstore (the 2nd floor) • Book signing next break. Get one!
  4. Today's talk • What is TypeProf? • How to use

    TypeProf effectively • Ruby's constant is • Conclusion 6
  5. What is TypeProf? Ruby Editor support • Error report, go

    to definition, completion, etc. • With minimal type annotations! 5.ti| 1 + "str" TypeError Do you mean: 5 .times
  6. Features • Type inference, error/warning report • Go to definition

    • Completion • Go to references • Go to type references • Automatic rename (method, constant) • Inline RBS (→ rbs-inline)
  7. Progresses since last RubyKaigi • Called for contributions • Got

    about 100+ PRs (Thanks all contributors!) • Supported for Ruby's full syntax • Created TypeProf.wasm (a demo in browser by ruby.wasm) • https://mame.github.io/typeprof.wasm/ • Improved for practical use cases • Fixed a bug of infinite-loop Today's main topics
  8. Today's talk • What is TypeProf? • How to use

    TypeProf (and recent improvements) • Ruby's constant is • Conclusion 11
  9. How to use TypeProf for your projects • Install VSCode

    "Ruby TypeProf" plugin • Put typeprof.conf.json (or jsonc) in the top folder • Reopen VSCode, and see if it works • If it doesn't work well, that's a good chance to contribute • For details, check my slide deck for RubyKaigi 2024 { "typeprof_version": "experimental", "rbs_dir": "sig/", }
  10. New features • diagnostic_severity and analysis_unit_dirs { "typeprof_version": "experimental", "rbs_dir":

    "sig/", "diagnostic_severity": "info", "analysis_unit_dirs": [ "lib/your_project/foo/", "lib/your_project/bar/", ] } New features
  11. "diagnostic_severity": change error level • TypeProf still reports many false

    positives • For a short-term solution, I provided a way to hide errors severity: "error" (default) "warning" "info" "hint" … and "none" are available
  12. "analysis_unit_dirs": separate analysis • You can specify directories to be

    analyzed together • For a large project, you need to separate analysis for each directory • API calls across file groups should be declared in RBS
  13. TypeProf infers types by default • … but as the

    project size grows, it will not scale lib/typeprof/core module TypeProf::Core class Service def update(path, text) ... end end end module TypeProf::LSP serv = Core::Service.new ... serv.update("path", "1+1") end (String, String) inferred lib/typeprof
  14. Separate analysis units • Stop type inference between units lib/typeprof/core

    module TypeProf::Core class Service def update(path, text) ... end end end module TypeProf::LSP serv = Core::Service.new ... serv.update("path", "1+1") end lib/typeprof/core lib/typeprof/lsp no inference
  15. Write RBS between analysis units module TypeProf::Core class Service def

    update(path, text) ... end end end module TypeProf::LSP serv = Core::Service.new ... serv.update("path", "1+1") end lib/typeprof/core lib/typeprof/lsp module TypeProf::Core class Service def update_file: (String, String) -> void end end sig/pub.rbs typecheck typecheck
  16. Today's talk • What is TypeProf? • How to use

    TypeProf (and recent improvements) • Ruby's constant is • Conclusion 19
  17. Background: Ruby's constant is too complex • Ruby's constant resolution

    • C::Foo • The current context has the priority • M::Foo, P::Foo, Q::Foo • The inheritance has the next priority • B::Foo, A::Foo, ::Foo • The scope has the last priority • (Note: Z::Foo is not searched) class P < Q end class A class B < Z class C < P include M Foo.new(...) end end end What could this Foo refer to?
  18. Constant analysis requires whole programs # myapp/main.rb class MyApp String.new(...)

    end # myapp/string.rb class MyApp class String end end This String should be ::String… No, that was MyApp::String
  19. Constant analysis requires whole programs # myapp/main.rb class MyApp String.new(...)

    end # myapp/string.rb module M class String end end class MyApp include M end This String should be ::String… Also indirectly makes MyApp::String accessible
  20. How to handle constants in TypeProf • Re-analyze existing constant

    resolutions: • when a new constant is defined • when the inheritance hierarchy is changed # myapp/main.rb class MyApp String.new(...) end # myapp/string.rb class MyApp class String end end MyApp::String is defined! Re-analyze all "String" references Updated: this is MyApp::String Assume that this is ::String
  21. How to handle constants in TypeProf • Re-analyze existing constant

    resolutions: • when a new constant is defined • when the inheritance hierarchy is changed # myapp/main.rb class MyApp String.new(...) end # myapp/string.rb class MyApp include M end This changes the inheritance! Re-analyze all references under MyApp Updated: this is MyApp::String Assume that this is ::String
  22. This mechanism caused an infinite-loop bug • Found by @alpaca-tc

    module M module M end end class MyApp include M end 0. There are ::M and ::M::M 1. This M should be ::M 2. This changes the inheritance hierarchy! Re-analyze all references under MyApp 3. Updated: This M should be ::M::M (!) 4. This changes the inheritance hierarchy! Re-analyze all references under MyApp 5. Updated: This M should be ::M Infinite loop!
  23. Similar problem was found in rbs-inline • Constant could be

    inconsistent between ruby and rbs-inline module M module M end end class MyApp include M include M end Ruby semantics: ::M::M rbs-inline semantics: ::M inconsistent
  24. Solution: Give up the inheritance search • … for the

    argument of include • Ruby's constant resolution • C::Foo • The current context has the priority • M::Foo, P::Foo, Q::Foo • The inheritance has the next priority • B::Foo, A::Foo, ::Foo • The scope has the last priority class P < Q end class A class B class C < P include M include Foo end end end What could this Foo refer to? TypeProf no longer searches for inheritance
  25. This mechanism caused an infinite-loop bug • Found by @alpaca-tc

    module M module M end end class MyApp include M end 0. There are ::M and ::M::M 1. This M should be ::M 2. This changes the inheritance! Re-analyze all references under MyApp 3. Updated: This M should be ::M::M (!) 4. This changes the inheritance! Re-analyze all references under MyApp 5. Updated: This M should be ::M Infinite loop! TypeProf no longer resolves this to ::M::M Fixed!
  26. Recap: Rewrite your Ruby Code • … if you want

    to use TypeProf or rbs-inline (or Sorbet) • Do not depend on the inheritance on constants module M module X end end class MyApp include M include X end module M module X end end class MyApp include M include ::M::X end Don't do this! Do this
  27. Today's talk • What is TypeProf? • How to use

    TypeProf (and recent improvements) • Ruby's constant is • Conclusion 30
  28. Conclusion • TypeProf is getting production-ready (hopefully) • Future work

    • Experiment with other than TypeProf itself • Still need many improvements • Contribution is truly welcome! • Come meet me at the venue bookstore→