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

Adding Type Signatures into Ruby Docs

Colby Swandale
September 08, 2022

Adding Type Signatures into Ruby Docs

Since Ruby's beginnings, its documentation has been maintained by people who help and support the language. Before the core team releases a new version of Ruby, contributors must update the documentation to reflect the current set of functionality, which presents many challenges to remaining consistent over Ruby's long history. One method may describe a set of arguments and the types one way, but another may tell them differently. Ruby 3 gained a highly requested feature, Type Annotations! A way to describe the structure of your Ruby Programs. In this talk, we'll look at improving Ruby's documentation by leveraging Ruby's Type Signatures to provide users with more accurate and consistent documentation.

Colby Swandale

September 08, 2022
Tweet

More Decks by Colby Swandale

Other Decks in Technology

Transcript

  1. to_s • Integer • Float • BigDecimal • Object Core

    Class Order • ARGF • Addrinfo • Array • Benchmark::Tms Alphabetical Order
  2. module Rake ## # A Task is the basic unit

    of work in a Rakefile. Tasks have associated # actions (possibly more than one) and a list of prerequisites. When # invoked, a task will first ensure that all of its prerequisites have an # opportunity to run and then it will execute its own actions. # # Tasks are not usually created directly using the new method, but rather # use the +file+ and +task+ convenience methods. # class Task # Create a task named +task_name+ with no actions or prerequisites. Use # +enhance+ to add actions and prerequisites. def initialize(task_name, app) ... end end end
  3. = Bug Triaging Guide This guide discusses recommendations for triaging

    bugs in Ruby's bug tracker. == Bugs with Reproducible Examples These are the best bug reports. First, consider whether the bug reported is actually an issue or if it is expected Ruby behavior. If it is expected Ruby behavior, update the issue with why the behavior is expected, and set the status to Rejected. bug_triaging.rdoc
  4. $ rdoc . Parsing sources... 100% [22/22] test/stringio/test_stringio.rb Generating Darkfish

    format into /Users/colby/Github/stringio/doc... Files: 22 Total: 236 (163 undocumented) 30.93% documented Elapsed: 0.3s
  5. $ rdoc . —format pot Parsing sources... 100% [897/897] yjit.c

    Generating POT format into /Users/colby/Github/ruby/docs... Files: 897 Total: 12129 (2858 undocumented) 76.44% documented Elapsed: 29.7s
  6. require 'rdoc' @rdoc = RDoc::RDoc.new @rdoc_options = RDoc::Options.load_options.tap |r| r.files

    = Dir[Dir.pwd] r.template = "rdoc" r.quiet = true end @rdoc.document @rdoc_options
  7. /* * call-seq: * empty? -> true or false *

    * Returns +true+ if the length of +self+ is zero, +false+ otherwise: * * "hello".empty? # => false * " ".empty? # => false * "".empty? # => true * */ static VALUE rb_str_empty(VALUE str) { return RBOOL(RSTRING_LEN(str) == 0); }
  8. /* * call-seq: * empty? -> true or false *

    * Returns +true+ if the length of +self+ is zero, +false+ otherwise: * * "hello".empty? # => false * " ".empty? # => false * "".empty? # => true * */ static VALUE rb_str_empty(VALUE str) { return RBOOL(RSTRING_LEN(str) == 0); } Description Call Sequence
  9. str[integer] = new_str str[integer, integer] = new_str str[range] = aString

    str[regexp] = new_str str[regexp, integer] = new_str str[regexp, name] = new_str str[other_str] = new_str String#[]
  10. str[integer] = new_str str[integer, integer] = new_str str[range] = aString

    str[regexp] = new_str str[regexp, integer] = new_str str[regexp, name] = new_str str[other_str] = new_str String#[]
  11. str[integer] = new_str str[integer, integer] = new_str str[range] = aString

    str[regexp] = new_str str[regexp, integer] = new_str str[regexp, name] = new_str str[other_str] = new_str String#[]
  12. str[integer] = new_str str[integer, integer] = new_str str[range] = aString

    str[regexp] = new_str str[regexp, integer] = new_str str[regexp, name] = new_str str[other_str] = new_str Call sequences are plain text
  13. # string.rbs class String include Comparable # https://github.com/ruby/rbs/blob/master/core/string.rbs # Returns

    a new String containing `other_string` # concatenated to `self`: # # "Hello from " + self.to_s # => "Hello from main” def +: (string other_str) -> String end
  14. class String include Comparable def gsub: (Regexp | string pattern,

    string replacement) -> String | (Regexp | string pattern, Hash[String, String] hash) -> String | (Regexp | string pattern) { (String match) -> _ToS } -> String | (Regexp | string pattern) -> ::Enumerator[String, self] end
  15. $ bundle exec steep check .....................................................................F main.rb:4:15: [error] Cannot pass

    a value of type `::Integer` as an argument of type `::String` ! ::Integer <: ::String ! ::Numeric <: ::String ! ::Object <: ::String ! ::BasicObject <: ::String ! ! Diagnostic ID: Ruby::ArgumentTypeMismatch ! " puts app.hello(1)
  16. def prepare_environment system "unzip #{ruby_src_download_path}” if release.has_type_signatures? system "gem unpack

    --target #{download_path.join("gems")} “ \ “#{download_path.join(“gems/rbs-*.gem”)}" end end
  17. def prepare_environment system "unzip #{ruby_src_download_path}” if release.has_type_signatures? system "gem unpack

    --target #{download_path.join("gems")} “ \ “#{download_path.join(“gems/rbs-*.gem”)}" end end
  18. def prepare_environment system "unzip #{ruby_src_download_path}” if release.has_type_signatures? system "gem unpack

    --target #{download_path.join("gems")} “ \ “#{download_path.join(“gems/rbs-*.gem”)}" end end
  19. def prepare_environment system "unzip #{ruby_src_download_path}” if release.has_type_signatures? system "gem unpack

    --target #{download_path.join("gems")} “ \ “#{download_path.join(“gems/rbs-*.gem”)}" end end
  20. @repository ||= RBS::Repository.new(no_stdlib: true).tap do |r| r.add(rbs_gem_path.join("stdlib")) end @loader =

    RBS::EnvironmentLoader .new(core_root: rbs_gem_path.join(CORE_PATH)) @environment = RBS::Environment.from_loader(@loader).resolve_type_names @builder = RBS::DefinitionBuilder.new(env: @environment) Setup RBS environment
  21. @repository ||= RBS::Repository.new(no_stdlib: true).tap do |r| r.add(rbs_gem_path.join("stdlib")) end @loader =

    RBS::EnvironmentLoader .new(core_root: rbs_gem_path.join(CORE_PATH)) @environment = RBS::Environment.from_loader(@loader).resolve_type_names @builder = RBS::DefinitionBuilder.new(env: @environment) Setup RBS environment
  22. @repository ||= RBS::Repository.new(no_stdlib: true).tap do |r| r.add(rbs_gem_path.join("stdlib")) end @loader =

    RBS::EnvironmentLoader .new(core_root: rbs_gem_path.join(CORE_PATH)) @environment = RBS::Environment.from_loader(@loader).resolve_type_names @builder = RBS::DefinitionBuilder.new(env: @environment) Setup RBS environment
  23. @repository ||= RBS::Repository.new(no_stdlib: true).tap do |r| r.add(rbs_gem_path.join("stdlib")) end @loader =

    RBS::EnvironmentLoader .new(core_root: rbs_gem_path.join(CORE_PATH)) @environment = RBS::Environment.from_loader(@loader).resolve_type_names @builder = RBS::DefinitionBuilder.new(env: @environment) Setup RBS environment
  24. if method_rdoc.type == “instance" @type_repository.signature_for_object_instance_method( object: object_rdoc.name, method: method_doc.name )&.map(&:to_s)

    elsif method_rdoc.type == “class" @type_repository.signature_for_object_class_method( object: object_rdoc.name, method: method_rdoc.name )&.map(&:to_s) end Fetching a method’s signatures
  25. method_type = RBS::TypeName.new( name: “String”, namespace: RBS::Namespace.root ) // instance

    methods @builder.build_instance(method_type).methods[:to_i] &.method_types // class methods @builder.build_singleton(method_type).methods[:to_i] &.method_types Query RBS for type signature
  26. String#gsub (::Regexp | ::string pattern, ::string replacement) -> ::String (::Regexp

    | ::string pattern, ::Hash[::String, ::String] hash) -> ::String (::Regexp | ::string pattern) { (::String match) -> ::_ToS } -> ::String (::Regexp | ::string pattern) -> ::Enumerator[::String, self] Example
  27. String#gsub (::Regexp | ::string pattern, ::string replacement) -> ::String (::Regexp

    | ::string pattern, ::Hash[::String, ::String] hash) -> ::String (::Regexp | ::string pattern) { (::String match) -> ::_ToS } -> ::String (::Regexp | ::string pattern) -> ::Enumerator[::String, self] Example
  28. (::int index) -> ::String? (::int start, ::int length) -> ::String?

    (::Range[::Integer] | ::Range[::Integer?] range) -> ::String? (::Regexp regexp) -> ::String? (::Regexp regexp, ::int | ::String capture) -> ::String? (::String match_str) -> ::String?
  29. (int index) -> String? (int start, int length) -> String?

    (Range[Integer] | Range[Integer?] range) -> String? (Regexp regexp) -> String? (Regexp regexp, int | String capture) -> String? (String match_str) -> String?
  30. (int index) -> String? (int start, int length) -> String?

    (Range[Integer] | Range[Integer?] range) -> String? (Regexp regexp) -> String? (Regexp regexp, int | String capture) -> String? (String match_str) -> String?
  31. (Integer index) -> String? (Integer start, Integer length) -> String?

    (Range[Integer] | Range[Integer?] range) -> String? (Regexp regexp) -> String? (Regexp regexp, Integer | String capture) -> String? (String match_str) -> String?
  32. Integer index -> String? Integer start, Integer length -> String?

    Range[Integer] | Range[Integer?] range -> String? Regexp regexp -> String? Regexp regexp, Integer | String capture -> String? String match_str -> String?
  33. Integer index -> String? Integer start, Integer length -> String?

    Range[Integer] | Range[Integer?] range -> String? Regexp regexp -> String? Regexp regexp, Integer | String capture -> String? String match_str -> String?
  34. (::int index) -> ::String? (::int start, ::int length) -> ::String?

    (::Range[::Integer] | ::Range[::Integer?] range) -> ::String? (::Regexp regexp) -> ::String? (::Regexp regexp, ::int | ::String capture) -> ::String? (::String match_str) -> ::String? Integer index -> String? Integer start, Integer length -> String? Range[Integer] | Range[Integer?] range -> String? Regexp regexp -> String? Regexp regexp, Integer | String capture) -> String? String match_str -> String?
  35. Checkout the other talks about Ruby Types! Types teaches success,

    what will we do? - @fugakkbn Let's collect type info during Ruby running and automaticall - @pink_bangbi Ruby programming with types in action - @soutaro