1 of 98

Introducing

Type Guard

to Steep

Takesh KOMIYA (@tk0miya)

タイトルテキスト

2 of 98

self.introduction

  • Takeshi KOMIYA (@tk0miya)
  • CTO of TIME INTERMEDIA Inc.
  • My first Ruby: 1.4.2 (maybe)
  • My first RubyKaigi: 2011
  • Member of asakusa-bashi_rbs, Gotanda.rb
  • OSS Developer
      • Sphinx (Doc. generator for python)
      • RBS ecosystem contributor

タイトルテキスト

3 of 98

self.introduction

  • Takeshi KOMIYA (@tk0miya)
  • CTO of TIME INTERMEDIA Inc.
  • My first Ruby: 1.4.2 (maybe)
  • My first RubyKaigi: 2011
  • Member of asakusa-bashi_rbs, Gotanda.rb
  • OSS Developer
      • Sphinx (Doc. generator for python)
      • RBS ecosystem contributor

タイトルテキスト

4 of 98

My works for RBS ecosystem

  • Update types
      • ruby-core and stdlibs (ruby/rbs)
      • 3rd party gems (ruby/gem_rbs_collection)
  • Development type generators
      • A maintainer of rbs_rails
      • The author of type generators
          • Rails (ActiveRecord, ActiveSupport, ActiveModel, etc.)
          • 3rd party gems (devise, discard, draper, config, etc.)

タイトルテキスト

5 of 98

My works for RBS ecosystem

  • Adjust Rubocop cops
      • Update official cops to allow RBS::Inline style comments
      • rubocop-rbs_inline gem
  • Develop reviewdog for Steep
  • Develop VSCode extension (rbs-helper)
      • Run RBS::Inline on save
      • Get quick feedback cycle with Steep

タイトルテキスト

6 of 98

My works for RBS ecosystem

  • Adjust Rubocop cops
      • Update official cops to allow RBS::Inline style comments
      • rubocop-rbs_inline gem
  • Develop reviewdog for Steep
  • Develop VSCode extension (rbs-helper)
      • Run RBS::Inline on save
      • Get quick feedback cycle with Steep

タイトルテキスト

7 of 98

タイトルテキスト

8 of 98

My works for RBS ecosystem

  • Contribute to RBS, Steep, RBS::Inline and IRB (katakata_irb)
      • Feature requests
          • steep:ignore
      • Implementations
          • silent mode
          • completion for lonely operator
          • completion for keyword arguments
      • Bug fixes

タイトルテキスト

9 of 98

My works for RBS ecosystem

  • Contribute to RBS, Steep, RBS::Inline and IRB (katakata_irb)
      • Feature requests
          • steep:ignore
      • Implementations
          • silent mode
          • completion for lonely operator
          • completion for keyword arguments
      • Bug fixes

タイトルテキスト

10 of 98

We’re hiring

We’re system integrator in Japan. Visit https://timedia.co.jp/

タイトルテキスト

11 of 98

Big thanks to my sponsors

Kazamori LLC.

Gold sponsors

Tea sponsors

タイトルテキスト

12 of 98

Introducing Type Guard to Steep

    • What’s Type Guard?
    • Type Guard for Union Types
    • User-defined Type Guard
    • Conclusion

タイトルテキスト

13 of 98

Introducing Type Guard

to Steep

タイトルテキスト

14 of 98

Introducing Type Guard to Steep

    • What’s Type Guard?
    • Type Guard for Union Types
    • User-defined Type Guard
    • Conclusion

タイトルテキスト

15 of 98

Could you find a bug?

タイトルテキスト

16 of 98

Could you find a bug?

To find a bug, let’s analyze this by Steep

タイトルテキスト

17 of 98

Could you find a bug?

Steep detects a bug in the code

タイトルテキスト

18 of 98

Could you find a bug?

Steep detects a bug in the code

Type `(::User | nil)` does not have method `name`

タイトルテキスト

19 of 98

Could you find a bug?

Steep detects a bug in the code

Type `(::User | nil)` does not have method `name`

Steep recognizes user’s type is (::User | nil)

タイトルテキスト

20 of 98

How do you fix?

Next, let’s fix the bug. How do you fix it?

タイトルテキスト

21 of 98

How do you fix?

Next, let’s fix the bug. How do you fix it?� My answer is nil-check

タイトルテキスト

22 of 98

How do you fix?

タイトルテキスト

23 of 98

How do you fix?

We confirm

user is not nil

by nil-check

タイトルテキスト

24 of 98

How do you fix?

We confirm

user is not nil

by nil-check

user is User | nil

user is User

user is nil

タイトルテキスト

25 of 98

How do you fix?

This behavior is called as “Type narrowing

user is User | nil

user is User

user is nil

We confirm

user is not nil

by nil-check

タイトルテキスト

26 of 98

How do you fix?

This behavior is called as “Type narrowing

user is User | nil

user is User

user is nil

Expression for type narrowing is called as “Type guard

タイトルテキスト

27 of 98

Steep and Type narrowing

Steep supports Type narrowing

        • if/else
        • ternary operator
        • logical operators (and, or)
        • case-when
        • while, until

タイトルテキスト

28 of 98

Steep and Type narrowing

Steep supports some type guards

        • nil-check
        • built-in methods
            • Object#is_a?, #kind_of?
            • Object#instance_of?
            • Object#nil?
            • Object#!
            • Object#===, Module#===
            • Module#<, Module#<=

タイトルテキスト

29 of 98

Steep and Type narrowing

Steep supports type narrowing by built-in methods�(e.g. is_a?)

タイトルテキスト

30 of 98

Steep and Type narrowing

Steep supports type narrowing by built-in methods�(e.g. is_a?)

Type guard found.

Let’s narrowing

タイトルテキスト

31 of 98

Steep and Type narrowing

Steep supports type narrowing by built-in methods�(e.g. is_a?)

user is narrowed

into User

Type guard found.

Let’s narrowing

タイトルテキスト

32 of 98

Interim Summary

    • Type narrowing is the process of making a variable’s type more specific
    • Type Guard is an expression for type narrowing
    • Steep supports type narrowing
    • Steep considers some Ruby built-in methods as type guards

タイトルテキスト

33 of 98

Introducing Type Guard to Steep

    • What’s Type Guard?
    • Type Guard for Union Types
    • User-defined Type Guard
    • Conclusion

タイトルテキスト

34 of 98

Narrowing by #present?

    • Do you know #present? method of ActiveSupport?
    • It checks the presence of object. It can be used like nil-check

タイトルテキスト

35 of 98

Narrowing by #present?

    • Do you know #present? method of ActiveSupport?
    • It checks the presence of object. It can be used like nil-check

We confirm

the user is present

タイトルテキスト

36 of 98

Narrowing by #present?

    • Do you know #present? method of ActiveSupport?
    • It checks the presence of object. It can be used like nil-check

We expect

user is narrowed to User

We confirm

the user is present

タイトルテキスト

37 of 98

Narrowing by #present?

    • Do you know #present? method of ActiveSupport?
    • It checks the presence of object. It can be used like nil-check

We confirm

the user is present

We expect

user is narrowed to User

タイトルテキスト

38 of 98

Narrowing by #present?

    • Do you know #present? method of ActiveSupport?
    • It checks the presence of object. It can be used like nil-check

Steep considers #present? is not

a type guard

We confirm

the user is present

We expect

user is narrowed to User

タイトルテキスト

39 of 98

Narrowing by #present?

    • We know #present? narrows the type runtime
    • But Steep doesn’t narrow the type by #present?
        • Steep considers #present? is not a Type Guard
        • Because Steep now supports built-in methods only
    • To resolve that, I proposed “Type Guard for Union Types

タイトルテキスト

40 of 98

Type Guard for Union Types

    • Expression satisfies followings is recognized as Type Guard
        • Method call
        • The receiver of the call is a variable or pure method call
        • The type of receiver is a union type
    • It narrows the receiver’s type by the return type of the call

(※)

(※) Pure method is a method with no side effects. See the document of Steep.

タイトルテキスト

41 of 98

Type Guard for Union Types

As a preparation, set the types of #present? up

タイトルテキスト

42 of 98

Type Guard for Union Types

Let’s check it again

タイトルテキスト

43 of 98

Type Guard for Union Types

Let’s check it again

Steep considers

this expression is a Type Guard

Because

タイトルテキスト

44 of 98

Type Guard for Union Types

Let’s check it again

Steep considers

this expression is a Type Guard

Because

  1. The expression is a method call

タイトルテキスト

45 of 98

Type Guard for Union Types

Let’s check it again

Steep considers

this expression is a Type Guard

Because

  • The expression is a method call
  • the receiver of the call is a variable

タイトルテキスト

46 of 98

Type Guard for Union Types

Let’s check it again

Steep considers

this expression is a Type Guard

Because

  • The expression is a method call
  • the receiver of the call is a variable
  • The type of receiver is a Union type

User | nil

タイトルテキスト

47 of 98

Type Guard for Union Types

Next, do narrowing

タイトルテキスト

48 of 98

Type Guard for Union Types

Next, do narrowing

This type guard contains

two method calls

タイトルテキスト

49 of 98

Type Guard for Union Types

Next, do narrowing

This type guard contains

two method calls

User#present?: () -> true

NilClass#present?: () -> false

タイトルテキスト

50 of 98

Type Guard for Union Types

Next, do narrowing

This type guard contains

two method calls

User#present?: () -> true

NilClass#present?: () -> false

タイトルテキスト

51 of 98

Type Guard for Union Types

Next, do narrowing

This type guard contains

two method calls

User#present?: () -> true

NilClass#present?: () -> false

Calls having truthy return type

is used to if-block narrowing

タイトルテキスト

52 of 98

Type Guard for Union Types

Next, do narrowing

This type guard contains

two method calls

User#present?: () -> true

NilClass#present?: () -> false

Calls having truthy return type

is used to if-block narrowing

Calls having falsey return type

is used to else-block narrowing

タイトルテキスト

53 of 98

Type Guard for Union Types

Next, do narrowing

Narrowed to User

because User#present? returns true

Narrowed to nil

because NilClass#present? returns false

タイトルテキスト

54 of 98

Type Guard for Union Types

Next, do narrowing

🎉🎉🎉🎉🎉

Narrowed to User

because User#present? returns true

Narrowed to nil

because NilClass#present? returns false

タイトルテキスト

55 of 98

Case of String#present?

String#present? returns both true and false

        • Returns false if empty, Otherwise true
        • The return type of String#present? becomes bool

タイトルテキスト

56 of 98

Case of String#present?

String#present? returns both true and false

        • Returns false if empty, Otherwise true
        • The return type of String#present? becomes bool

        • String will be applied to both if/else block

タイトルテキスト

57 of 98

Case of String#present?

IO#gets returns String | nil

タイトルテキスト

58 of 98

Case of String#present?

IO#gets returns String | nil

String#present?: () -> bool

NilClass#present?: () -> false

タイトルテキスト

59 of 98

Case of String#present?

String#present?: () -> bool

NilClass#present?: () -> false

Narrowed to String

because String#present? returns bool

Narrowed to String | nil

because String#present? returns bool

and NilClass#present? returns false

IO#gets returns String | nil

タイトルテキスト

60 of 98

Current status of

Type Guard for Union Types

    • I proposed Type Guard for Union Types to Steep
        • soutaro/steep#1497

タイトルテキスト

61 of 98

Current status of

Type Guard for Union Types

    • I proposed Type Guard for Union Types to Steep
        • soutaro/steep#1497
        • Released as Steep-1.10

🎉

タイトルテキスト

62 of 98

Current status of

Type Guard for Union Types

    • I proposed Type Guard for Union Types to Steep
        • soutaro/steep#1497
        • Released as Steep-1.10
    • I also modified the type of #present? and #blank?
        • ruby/gem_rbs_collection#815

タイトルテキスト

63 of 98

Current status of

Type Guard for Union Types

    • I proposed Type Guard for Union Types to Steep
        • soutaro/steep#1497
        • Released as Steep-1.10
    • I also modified the type of #present? and #blank?
        • ruby/gem_rbs_collection#815
        • Merged now

🎉

タイトルテキスト

64 of 98

Interim Summary

I introduced Type Guard for Union Types to Steep

        • Some kinds of method calls for Union are considered as Type Guard
        • It narrows the type of receiver using type definition
        • Now available in Steep-1.10
        • Types of ActiveSupport is available

タイトルテキスト

65 of 98

Introducing Type Guard to Steep

    • What’s Type Guard?
    • Type Guard for Union Types
    • User-defined Type Guard
    • Conclusion

タイトルテキスト

66 of 98

Narrowing by own method

In real world apps, we often have own methods that determine the class or a state of the object

タイトルテキスト

67 of 98

Narrowing by own method

In real world apps, we often have own methods that determine the class or a state of the object

It would be great if Steep supports them. Let’s go!

タイトルテキスト

68 of 98

Narrowing by own method

    • Let’s talk about User#admin?
    • There are three classes
        • 1 superclass
        • 2 subclasses
    • User has a checker method�named `#admin?`

タイトルテキスト

69 of 98

Narrowing by own method

This code calls #admin? to classify user to admin or not

タイトルテキスト

70 of 98

Narrowing by own method

This code calls #admin? to classify user to admin or not

We confirm

the user is an admin

タイトルテキスト

71 of 98

Narrowing by own method

This code calls #admin? to classify user to admin or not

We expect

user is narrowed to AdminUser

We confirm

the user is an admin

タイトルテキスト

72 of 98

Narrowing by own method

This code calls #admin? to classify user to admin or not

We expect

user is narrowed to AdminUser

We confirm

the user is an admin

タイトルテキスト

73 of 98

Narrowing by own method

This code calls #admin? to classify user to admin or not

We expect

user is narrowed to AdminUser

Steep considers #admin? is not

a type guard

We confirm

the user is an admin

タイトルテキスト

74 of 98

Narrowing by own method

    • We often have own methods that determine the class or a state of the object
    • But Steep considers own method is not a Type Guard
        • Steep now supports built-in methods only
    • To resolve that, I proposed “User-defined Type Guard

タイトルテキスト

75 of 98

User-defined Type Guard

User-defined Type Guard is declared as annotated method

タイトルテキスト

76 of 98

User-defined Type Guard

User-defined Type Guard is declared as annotated method

        • The annotation starts with “guard:”

タイトルテキスト

77 of 98

User-defined Type Guard

User-defined Type Guard is declared as annotated method

        • The annotation starts with “guard:”
        • The annotation has predicate after the prefix

タイトルテキスト

78 of 98

User-defined Type Guard

User-defined Type Guard is declared as annotated method

        • The annotation starts with “guard:”
        • The annotation has predicate after the prefix
            • It describes how it narrows the type when the method call is evaluated as truthy

The type of receiver �will be narrowed to AdminUser

タイトルテキスト

79 of 98

User-defined Type Guard

The predicate consists of subject, verb and type

        • Subject indicates the target of narrowing
        • Verb describes how to narrow the type
        • Type describes the type after narrowing

Subject verb Type

“self”

argument name

“is”

“is_a”

Type name

“self”

argument name

タイトルテキスト

80 of 98

User-defined Type Guard

The predicate consists of subject, verb and type

        • Subject indicates the target of narrowing
        • Verb describes how to narrow the type
        • Type describes the type after narrowing

        • e.g.
            • self is String
            • arg1 is Integer

    • self is arg1
    • self is_a arg2

Subject verb Type

タイトルテキスト

81 of 98

Narrowing by

User-defined Type Guard

Let’s analyze the code with User-defined Type Guard

タイトルテキスト

82 of 98

Narrowing by

User-defined Type Guard

Let’s analyze the code with User-defined Type Guard

This call is considered as type guard

by declaration

タイトルテキスト

83 of 98

Narrowing by

User-defined Type Guard

Let’s analyze the code with User-defined Type Guard

🎉🎉🎉

This call is considered as type guard

by declaration

Narrowed to AdminUser

based on the predicate

“self is AdminUser”

タイトルテキスト

84 of 98

Interim Summary

    • User-defined Type Guard can narrow the type
    • You can change any method to type guard by annotation
    • At present, it’s under development
        • soutaro/steep#1501
        • “self is TYPE” style predicate is available

タイトルテキスト

85 of 98

User-defined Type Guard

Limitations (1)

User-defined Type Guard cannot “change” the type

        • It can be used only for “narrowing”
        • The new type must be a subtype of the original type

Invalid.

Object is not

a subtype of User

タイトルテキスト

86 of 98

User-defined Type Guard

Limitations (2)

The type in else-block could not be narrowed well

        • Steep attempts to calculate the type in the else-block,�But the processing sometimes fails
        • In AdminUser’s case, it failed to calculate the type �“User but not AdminUser”
            • As a result, its type is remained unchanged as “User”
        • Narrowing goes well if calculation succeeded

タイトルテキスト

87 of 98

Advanced usage

Conditional Types

User-defined Type Guard can represent conditional types

        • e.g. title is optional by default, but required if published

タイトルテキスト

88 of 98

Advanced usage

Conditional Types

    • User-defined Type Guard can represent conditional types

タイトルテキスト

89 of 98

Advanced usage

Conditional Types

    • User-defined Type Guard can represent conditional types
        • Add a virtual constraint module for “published” state

タイトルテキスト

90 of 98

Advanced usage

Conditional Types

    • User-defined Type Guard can represent conditional types
        • Add a virtual constraint module for “published” state
        • Use it in type guard predicate

タイトルテキスト

91 of 98

Advanced usage

Conditional Types

    • User-defined Type Guard can represent conditional types
        • Add a virtual constraint module for “published” state
        • Use it in type guard predicate

article is narrowed to

Article & Published

#title is also narrowed

String from String?

タイトルテキスト

92 of 98

Advanced usage

Conditional Types

    • Apps usually uses conditional types technique
    • ActiveRecord provides conditional columns
        • Delegated types
        • validations (#valid?)
    • I’ll propose supporting conditional types to rbs_rails after merging User-defined Type Guard into Steep

タイトルテキスト

93 of 98

Introducing Type Guard to Steep

    • What’s Type Guard?
    • Type Guard for Union Types
    • User-defined Type Guard
    • Conclusion

タイトルテキスト

94 of 98

Conclusion

    • Type Narrowing helps you to write Ruby code more safe
    • I proposed two Type Guards to Steep
        • Type Guard for Union Types
        • User-defined Type Guard
    • Please enjoy types with Steep!

タイトルテキスト

95 of 98

Acknowledges

    • soutaro
    • pocke
    • ksss
    • tompng
    • #types channel in ruby-jp slack
    • and all types fans

タイトルテキスト

96 of 98

タイトルテキスト

97 of 98

タイトルテキスト

98 of 98

Current status of

Type Guard for Union Types

    • We choose bool for the return value of Object#present?
        • Almost subclasses will return true
        • But some subclasses will return both true and false
            • Developers can change the behavior via overriding #empty? method
    • Type narrowing will not work well in else-block
        • Please override the type if you need it

タイトルテキスト