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

Writing Ruby Scripts with TypeProf

Writing Ruby Scripts with TypeProf

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→