00:00:05.120
hi
00:00:12.280
everybody all right welcome to inline
00:00:14.960
ABS comments for seamless type checking
00:00:17.119
with Sor my name is Alexander Terza uh
00:00:20.960
you can reach out to me by email because
00:00:22.800
I don't use Twitter um or on
00:00:25.800
GitHub i'm a senior staff engineer at
00:00:28.320
Shopify i'm working in the Ruby and RS
00:00:30.560
infrastructure team uh more precisely in
00:00:32.320
the Ruby developer experience team you
00:00:34.559
may have heard of us uh for projects
00:00:36.399
such as the Ruby LSP tapia which is a
00:00:39.120
companion tool for so or contribution to
00:00:42.920
so we actually were created like our
00:00:46.719
team was created to introduce so at
00:00:48.719
Shopify in February 2019 where um we
00:00:52.960
were early adopters of so a few months
00:00:55.199
before it was open sourced and we
00:00:58.239
realized the need to have type checking
00:00:59.840
for such large code base a few years
00:01:02.559
after that ABS was created around 2021
00:01:06.479
and for Ruby3 and this is also around
00:01:08.880
this time that steep was open
00:01:11.400
source so as the Ruby developer
00:01:14.080
experience team our goal is to
00:01:16.320
understand what our developers want and
00:01:19.840
what we should be doing for them to do
00:01:22.560
that we run surveys every year and we
00:01:25.520
ask them question about like what are
00:01:27.200
the things that you need so for example
00:01:29.759
since we introduced typing using so at
00:01:32.000
Shopify we asked them like do you want
00:01:34.320
more code to be typed like is type are
00:01:36.320
the types useful for you and do you need
00:01:38.079
more and while at the beginning when we
00:01:40.320
introduced it it was not very clear if
00:01:42.400
they really want it or not if we look at
00:01:44.320
our most recent edition of the survey
00:01:46.560
80% of them are like yes I need more
00:01:48.640
types this is super
00:01:50.200
useful the same when we ask them do you
00:01:52.640
want so to be used in more code bases at
00:01:55.200
Shopify they're like yes 7 70% of them
00:01:58.159
are like Yes this is super useful if we
00:02:00.399
looked at the beginning though that was
00:02:01.920
not that clear but people came to
00:02:04.399
understand and to appreciate the
00:02:06.240
benefits of static typing gradual typing
00:02:10.640
uh when we ask them do you want to have
00:02:12.239
to write less annotations in your code
00:02:14.800
it's not really something that bother
00:02:16.640
them in majority like 55% of them are
00:02:19.280
saying like yes that would be cool but
00:02:20.640
it's not like as obvious as pre previous
00:02:23.720
questions but when we ask them do you
00:02:26.640
want a friendlier syntax to express the
00:02:29.040
types in your code 75% of them are like
00:02:31.599
yes we need something
00:02:33.080
better if you haven't been introduced to
00:02:35.599
so before let me give you an example of
00:02:37.280
what it looks like uh first we're going
00:02:40.319
to express the signatures for methods
00:02:42.800
using the sig calls here directly in the
00:02:45.680
Ruby code saying I'm having a signature
00:02:47.920
for this attribute reader for example or
00:02:49.599
a signature for this initialize the
00:02:51.680
signature express the params that I used
00:02:54.080
in the sign in um that I use by the
00:02:56.400
method and I'm going to say that the
00:02:58.560
written type is
00:03:00.440
whatever because this is actual Ruby
00:03:02.959
code this sig method needs to be defined
00:03:05.760
somewhere and to be able to use that you
00:03:08.000
need to also depend on the sovereign
00:03:09.760
runtime gem that defines this sig method
00:03:12.319
here somewhere and to make it available
00:03:14.720
in your class you need to extend tig
00:03:17.440
directly in your class so this is
00:03:19.280
already a lot of clutter this is not the
00:03:21.280
be the most beautiful Ruby I've seen in
00:03:23.040
my life and if you see this really easy
00:03:25.840
piece of code here simple piece of code
00:03:27.760
almost half of it is already type
00:03:30.519
annotations when we want to express
00:03:32.560
types you we have to go around the Ruby
00:03:34.560
syntax because it's actually Ruby code
00:03:36.080
so we have to find ways with the Ruby
00:03:37.680
syntax to actually express the types
00:03:39.360
that we have in our code so for example
00:03:40.879
here I'm having a nable of a location uh
00:03:43.760
I can say that my my block for my method
00:03:46.560
is optional by having again a tailable
00:03:49.200
call and I'm expressing the block with
00:03:50.879
t.prock this is getting very painful to
00:03:53.440
write when you have to do that on every
00:03:55.360
method that you have in your
00:03:57.799
codebase uh we can also uh um annotate
00:04:01.920
the type of instance variables local
00:04:03.760
variables these kind of things and again
00:04:05.680
we have to go around with the t.let at
00:04:07.519
to express that I want to set the type
00:04:09.519
of this variable and this is going to be
00:04:11.360
an array of node this is again very
00:04:13.400
painful so we need a friendlier syntax
00:04:16.479
for that this is not something we like
00:04:18.959
to use
00:04:20.440
everywhere when Ruby 3 was released
00:04:23.280
there was like a glimmer of hope because
00:04:25.360
uh it was coming with ABS that was a new
00:04:27.759
language to describe types for Ruby
00:04:30.400
programs and we were super excited
00:04:32.160
because we really wanted to jump on this
00:04:33.919
wagon and say like okay we're going to
00:04:35.360
use ABS now you can replace the service
00:04:36.880
so syntax by this uh sadly we did a few
00:04:40.400
tries and the concept of ABS is you have
00:04:43.440
your Ruby file that is proper Ruby code
00:04:46.479
and you have a companion file that is
00:04:48.080
the RBS file in which you're going to
00:04:50.000
express the types that you were going to
00:04:51.520
use in your program so first problem
00:04:53.840
it's a lot of duplication for every file
00:04:56.320
you have this companion file that's
00:04:58.400
coming with it and for us we have
00:05:01.280
something like 75,000 files in our
00:05:03.600
monolith 1.5 million methods that is a
00:05:07.440
lot of duplication we need to to do and
00:05:09.360
imagine opening the pull request to
00:05:10.800
GitHub by adding the by doubling the
00:05:13.120
size of your codebase that will not fly
00:05:16.720
the other issue is that you cannot
00:05:18.720
express types for local variables for
00:05:21.039
example in your code and you cannot do
00:05:22.880
things like casts or um the setting the
00:05:26.880
type of um I already said that local
00:05:29.120
variables so that was also an issue for
00:05:31.600
proper type checking and in-depth type
00:05:34.680
checking so what we actually really
00:05:36.880
wanted was a mix of the two words wanted
00:05:40.080
to be in a Ruby file and be able to
00:05:42.479
express the types directly by using the
00:05:44.800
nice nicer syntax that is but directly
00:05:47.919
in the Ruby file and for example we can
00:05:50.479
do that using comments we can add a
00:05:52.000
comment on top of the attribute reader
00:05:53.840
or on top of the method saying that here
00:05:55.919
is the type that the signature for my uh
00:05:58.560
for my
00:05:59.720
method and because I'm in a comment now
00:06:02.160
I'm not like bound to the Ruby syntax
00:06:04.479
and I can express using the LBS syntax
00:06:07.039
what is the type of my parameter for
00:06:08.880
example I'm having an label of location
00:06:11.360
using this question mark or I can set
00:06:13.759
the type of my proc by my block sorry by
00:06:16.560
using this syntax this is much
00:06:18.919
nicer and if we're using comments we
00:06:21.280
also can put them in different places
00:06:22.960
and for example use them as well to
00:06:24.560
define the type of the instance
00:06:27.720
variables because we use comments we
00:06:30.160
don't have to support this sig method we
00:06:32.319
don't have to have any kind of runtime
00:06:34.160
dependency that may had an overhead for
00:06:36.639
example uh when I'm running my code uh
00:06:38.960
in production so no more runtime
00:06:40.639
dependency nice syntax just comments
00:06:43.680
this is exactly what we want so how do
00:06:46.240
we get
00:06:47.319
there first we have to choose like a
00:06:49.600
type checker to do this with of course
00:06:51.759
we're using a lot of so at Shopify we
00:06:53.520
have more than 600 codebases using so in
00:06:55.840
our company so that was a strong
00:06:57.440
incentive to go with so and continue
00:06:59.039
with it because we don't want to break
00:07:00.800
like the work we already did or monolith
00:07:04.000
also saw like a lot of a very good
00:07:05.919
adoption 99% of the files we have in the
00:07:08.560
monolith are using sorbet and are typed
00:07:10.639
by sorbet uh all the most of the
00:07:13.280
important methods that we use already
00:07:14.880
have a signature and six more than 60%
00:07:16.960
of the calls we do in our monolith are
00:07:19.280
against the method that comes with a
00:07:22.360
signature there is another type checker
00:07:24.479
for for Ruby that is called steep that
00:07:26.240
was introduced by Sutaro uh we did try
00:07:28.800
we tried to use it uh most the main most
00:07:31.759
important problem here is X speed
00:07:33.280
because SIP is written in Ruby it's very
00:07:35.199
hard to compete with a C++
00:07:36.880
implementation that is highly
00:07:38.080
parallelizable
00:07:39.599
um so when we try it on small code bases
00:07:41.759
it does work it scale um we can type
00:07:44.800
check most of what we already had when
00:07:47.120
we run it against the monolith um
00:07:49.440
something that we can type check in
00:07:51.039
under 20 seconds with so takes more than
00:07:54.080
an hour with steep so this is not
00:07:55.919
something we can run on CI for example
00:07:58.080
because we deploy much more often often
00:08:00.479
so that was a problem so we were like
00:08:02.240
facing two pro two choices here or we
00:08:05.160
can make so support ABS syntax or we can
00:08:09.039
invest a lot of time and steep to make
00:08:10.639
it faster and
00:08:13.000
scalable when we looked at the kind of
00:08:15.039
features that were like defined in so
00:08:17.759
defined in so and that we could express
00:08:19.599
with ABS the coverage was really good
00:08:21.840
most of the features we have in so have
00:08:23.520
an equivalent in the LBS syntax so we
00:08:25.599
can do the replacement um just some of
00:08:28.479
the syntax actually existing in ABS does
00:08:31.120
not have an equivalent in sit so that
00:08:32.719
was not a problem we can just like
00:08:33.919
forbid it when you're trying to write it
00:08:36.320
and for most of the features we have an
00:08:38.000
equivalent the only the only problem
00:08:40.080
here was the lower bound on generics
00:08:42.080
that you cannot express that in so but I
00:08:44.080
believe this is something we can
00:08:45.800
change so do a few features that we
00:08:48.560
cannot express um in ABS and it will not
00:08:51.040
make sense to bring to ABS for example
00:08:52.800
the concept of abstract classes or
00:08:54.320
abstract methods uh for those we will
00:08:57.040
have to find a workar around i'm going
00:08:58.480
to show you that later if you want to
00:09:00.880
know more about AirBS and so syntax and
00:09:04.959
the specification be behind them you can
00:09:07.120
take a look at one of my previous talk
00:09:08.959
um that is gradual typing for Ruby
00:09:10.880
comparing AirBS and
00:09:13.640
Airbus Rubik
00:09:17.040
so let's see how we can start using ABS
00:09:19.680
comments for type checking with
00:09:22.360
so the first problem was the Ruby RBS
00:09:25.360
parser the RBS parser was built around
00:09:28.880
the Ruby VM so to pass a piece of RBS
00:09:31.360
code you had to have a Ruby VM running
00:09:34.000
that means that if we wanted to use this
00:09:37.279
RBS parser from the Sway C++
00:09:39.600
implementation we also needed to embed a
00:09:42.000
uh Ruby VM in the C++ implementation
00:09:44.399
that's a problem especially when you do
00:09:46.399
a lot of parallelized uh analysis where
00:09:48.959
we're going to fight with the GVL a lot
00:09:51.680
so we actually fixed this problem and I
00:09:53.839
hope you had a chance to see my
00:09:55.279
colleague Alexander yesterday that was
00:09:57.279
explaining oh we transformed the Ruby
00:09:58.880
Airbs passer to be a pure C
00:10:01.279
implementation so we can include it in
00:10:03.200
the C++ so with absolutely no
00:10:07.000
problem and from there we needed to find
00:10:10.399
a solution to use this RBS parser inside
00:10:12.399
so let me give you an over just a
00:10:15.120
simplified overview of how so is doing
00:10:17.120
the typeing we're having this what we
00:10:18.959
call a typeeing pipeline here where In
00:10:21.440
input I'm going to give Ruby files and
00:10:23.519
in output I'm going to have the type
00:10:25.200
checking errors if any that are going to
00:10:27.360
be displayed on my
00:10:28.839
screen the very first step is to parse
00:10:31.440
the code i'm having those files that I
00:10:33.760
need to transform in something that is
00:10:35.440
more like easier to analyze so we're
00:10:37.920
going to build an abstract syntax tree
00:10:39.760
from this this is just a tree of nodes
00:10:41.680
representing the content of our Ruby
00:10:44.440
file the second step is what is called
00:10:47.040
dshuga we're actually going to rewrite
00:10:49.279
the a we got at the previous phase into
00:10:51.839
another a that is that I'm going to call
00:10:54.240
like simplified we're just going to
00:10:55.680
remove variation in inside this a let me
00:10:58.399
give you an example so in Ruby I can use
00:11:01.040
a lot of syntactic sugar for example I
00:11:03.200
can express the inverse of if using the
00:11:06.160
unless keyword and I can say that unless
00:11:09.200
ag is empty I'm going to call argv.shift
00:11:12.040
shift and I'm going to use another
00:11:14.240
syntactic sugar to do a safe navigation
00:11:16.079
and call to i on it only if it's not
00:11:18.600
nil dugaring the code is removing this
00:11:21.360
syntactic sugar I'm going to just find
00:11:23.920
an equivalent way to express the same
00:11:25.920
thing with less variation so for example
00:11:28.160
a nless can be changed into a if if I
00:11:30.640
negate the the condition and a safe
00:11:33.360
navigation can be translated in just
00:11:35.519
like adding a if gu to say I'm going to
00:11:37.680
call to i only if the result of a dot
00:11:40.079
shift avshift is not new so by doing
00:11:43.519
this dshugaring we just make the work of
00:11:45.600
subsequent phases easier because they
00:11:48.000
don't have to care about like all the
00:11:49.760
variations of the
00:11:51.959
syntax once I'm having this simplified a
00:11:56.079
I can resolve it which basically is
00:11:58.320
creating a knowledge base about what is
00:12:00.079
in the code what are the relationships
00:12:03.040
between a classes for example through
00:12:05.040
inheritance or like with modules with
00:12:06.959
mixins and just keeping track of like
00:12:09.279
what exists in the codebase and all
00:12:10.880
types are
00:12:11.880
related another phase is going to be to
00:12:14.959
to build the control for graph which is
00:12:16.800
a graph of nodes explaining what is the
00:12:18.800
flow of information in blocks of codes
00:12:21.279
so for example I'm having a variable
00:12:22.800
it's going to go in if and in this
00:12:24.399
branch I'm going to declare another
00:12:25.680
variable this kind of things using both
00:12:28.480
this information the control for graph
00:12:30.320
and the global state I can start type
00:12:32.720
checking the the code I can flow my
00:12:34.639
types in a piece of code and get the
00:12:36.959
type and get the type checking errors if
00:12:39.560
any to support RBS what we did was to
00:12:43.200
add a new phase in between the parsing
00:12:45.680
and the
00:12:46.600
disharing this phase is here to rewrite
00:12:50.800
the a we got from the parser into a new
00:12:53.040
a that has type information that we want
00:12:55.920
to feed to the rest of the pipeline by
00:12:58.399
doing so we can just add this step at
00:13:00.079
the beginning and we very we limit the
00:13:03.200
blast radius of our change the rest of
00:13:05.200
the pipeline doesn't have to care about
00:13:06.560
ABS at all let me give you an example of
00:13:08.959
how that works so as an input I'm having
00:13:11.760
this file with the RBS comments please
00:13:14.720
keep in mind I'm in the so process this
00:13:17.040
is static analysis this is not actually
00:13:19.200
running the code Ruby the Ruby code it's
00:13:21.440
just doing analysis over the code not
00:13:23.440
actually executing it so this is what
00:13:25.279
the Ruby static analyzer sees when it's
00:13:28.079
analyzing my code and trying to find
00:13:30.000
type checking errors so it's seeing
00:13:32.560
those comments and what we want to do is
00:13:35.279
to rewrite the code so SB sees it as if
00:13:38.639
it had like actual SIG calls inside it
00:13:42.240
again I'm not executing this this is
00:13:43.839
just what Sorb is saying so I'm going to
00:13:45.839
take this comment saying I am having an
00:13:47.519
array of node and I'm going to to create
00:13:49.519
the equivalent sig returns array of node
00:13:52.800
uh for sor to see it and so the rest of
00:13:54.880
the type checking pipeline is going to
00:13:56.560
see as I was using sig calls in the
00:14:00.040
middle so for signatures I can express
00:14:03.040
them for example for attribute readers
00:14:04.959
and different attribute accessors by the
00:14:06.720
hash colon comment on top of it and for
00:14:10.480
methods I can use the hashtag comment
00:14:12.320
with the parenthesis the arrow to say
00:14:14.320
the return type I can also split it over
00:14:16.560
multiple lines if I have a very long
00:14:18.079
line and I want to please rubocop these
00:14:20.000
kind of things so we're going to take
00:14:21.680
those comments and translate them into
00:14:23.440
the actual six syntax that so knows for
00:14:27.279
it to type check properly
00:14:29.920
how does that work i'm going to take a
00:14:31.440
very simple a simplified example here so
00:14:33.440
my defu method as a comment on top of it
00:14:36.320
that says it returns void and my
00:14:37.920
attribute reader bar is returning a
00:14:40.760
string when we're running the pass phase
00:14:43.199
on this we're getting back the
00:14:45.720
a and the table of comments that exists
00:14:49.120
in the code we just
00:14:51.240
passed then we're going to visit this a
00:14:54.320
and for each node we're going to check
00:14:55.839
is there a comment that is related to
00:14:57.600
this node so I'm going to find my defaf
00:14:59.680
here looking that oh my def is at line
00:15:01.839
two is there a comment in the previous
00:15:04.360
line that has not been consumed by
00:15:06.800
another node yet so I'm going to look in
00:15:08.560
my table here and see oh okay yeah there
00:15:10.560
is a comment line one so I'm going to
00:15:12.800
associate it to my dev then I'm
00:15:15.440
continuing visiting and I'm going to
00:15:17.360
find this attribute reader at five at
00:15:19.360
line five I'm going to look in my table
00:15:21.040
and say oh is there a comment that is
00:15:23.040
not consumed yet that is related that is
00:15:25.839
before line five and I'm going to find
00:15:27.839
this comment at line four okay
00:15:29.959
associated once I am having this node
00:15:32.800
versus comment association I just have
00:15:34.720
to do the rewriting i'm going to change
00:15:37.199
my est to introduce the s calls that so
00:15:40.320
is going to see as the type-checking
00:15:42.480
artifact it
00:15:43.880
needs so I'm going to introduce a se
00:15:46.240
call before def and I'm going to
00:15:48.000
introduce a sig call before attribute
00:15:50.639
reader to say like oh here are the
00:15:52.480
parameters and here's the return type
00:15:54.560
again I'm not executing this this is
00:15:56.320
just what so sees when it's analyzing
00:15:58.399
the code
00:15:59.720
statically and so for it so is actually
00:16:02.320
just going to see s calls in the middle
00:16:04.320
and it's going to type check it
00:16:06.440
properly we are actually going one step
00:16:08.720
further when we created the S calls here
00:16:10.880
we're lying to so and we're telling it
00:16:12.720
the location of this SQL is somewhere in
00:16:14.880
the comment so I'm passing back the
00:16:16.480
location that I I got from my table here
00:16:19.279
to my SQL to stale so like this thing is
00:16:22.639
actually in the comment at the command
00:16:24.240
location that enables in the so LSP when
00:16:27.120
you hover over something in the comment
00:16:28.959
or you come and click on something in
00:16:30.720
the comment all the power of the LSP
00:16:33.040
actually works and I can over type in
00:16:35.759
the comment giving you like oh here here
00:16:38.160
is the definition of a comment and you
00:16:40.320
can see what is like the the comment of
00:16:42.160
this comment or you can do jump to
00:16:44.720
navigation when you come and click for
00:16:48.600
example um on top of signatures we also
00:16:51.360
have inline assertions inline assertions
00:16:53.199
are here to define the type of local
00:16:54.800
variables or instance variables and you
00:16:56.560
can do multiple things with them you can
00:16:58.160
define the type of a variable with the
00:17:00.320
hashtag colon the type of the variable
00:17:02.160
after that in so you will do t.let and
00:17:05.039
then your variable and finally the co
00:17:07.039
the the type for this you can also cast
00:17:10.000
statically cast the type of a variable
00:17:11.919
when you want to change this type so I
00:17:13.520
had something that was here a node and I
00:17:15.760
know it's actually a tree so I'm going
00:17:17.120
to cast it with hashtag colon as the
00:17:19.839
type of my thing in so you will use
00:17:22.959
t.cast for this and there is a specific
00:17:26.319
kind of cast that is casting to the
00:17:28.319
non-neable version of this thing so I
00:17:30.559
just want to remove remove the nable
00:17:32.480
attribute from it i'm having a nable of
00:17:34.559
node and I know it's not neil so I can
00:17:36.559
cast it to as not nil with the syntax
00:17:39.440
which will be the t do me equivalent in
00:17:43.000
so and we use the exact same approach m
00:17:46.320
mapping the comments directly to the
00:17:48.000
nodes when we're going to visit so
00:17:50.000
outside of the so we have this piece of
00:17:51.600
code here we're going to pass it we're
00:17:53.600
getting the comment table and the a and
00:17:55.679
we're going to start visiting it and
00:17:57.360
find okay I'm having an assign I'm going
00:17:59.039
to look at its return at this value the
00:18:01.360
value finishes line one column 8 do I
00:18:03.919
have a comment after this just that
00:18:05.919
exists so I am having one at like the
00:18:07.919
column 9 so I can associate it with this
00:18:10.080
node going to continue visiting I'm
00:18:11.919
finding the call to bar I'm going to
00:18:13.360
visit the arguments I'm finding this
00:18:15.360
local variable here that is finishing
00:18:17.360
column four at line four I'm going to
00:18:19.039
look my table I'm having a comment okay
00:18:21.360
I'm associating it and the same thing
00:18:23.120
for the return value finding some at
00:18:25.760
column 10 associating it here and
00:18:28.160
finally I'm going to rewrite it so I'm
00:18:29.919
having those nodes with the comment
00:18:31.120
association and I'm going to introduce
00:18:33.440
the nodes in the that represent the
00:18:35.200
calls that I will have to t dot let t
00:18:37.039
dot cast t dot must and using t label
00:18:39.600
and all the apparatus of
00:18:42.520
server I can do this common association
00:18:44.960
with also pieces of expression for
00:18:46.799
example when I need to cast or uh when I
00:18:49.520
need to cast for example a musts in the
00:18:51.120
middle of an expression so I can split
00:18:52.960
it into multiple lines sadly Ruby only
00:18:55.120
supports end of the line comments with
00:18:56.720
the hashtag if we add inline comments
00:18:58.960
that will be even better if you saw the
00:19:01.440
bit like the the Ruby against the world
00:19:03.360
Ruby committers against the world this
00:19:04.720
morning maybe one day it will exist so
00:19:07.120
if you want to use those um hashtag
00:19:09.520
comments on expressions you have to
00:19:11.200
split your expression over multiple
00:19:12.640
lines so here I can express that my at
00:19:14.960
nodes the dot first call I'm going to
00:19:17.280
cast the return value to a tree then on
00:19:20.240
this tree I'm going to call nodes again
00:19:21.919
and call do first and I'm going to cast
00:19:24.320
the ver the return of first as non label
00:19:26.400
because I know I have at least one and
00:19:28.400
finally I'm going to call name on it i
00:19:30.799
can do the same thing for arguments and
00:19:32.960
split my call over multiple lines to pre
00:19:36.000
to specify the type of the arguments and
00:19:38.799
here I'm also taking in consideration
00:19:40.320
the comma that may exist and just
00:19:41.760
looking is there a comment after the
00:19:44.520
comma you can use those command those um
00:19:48.000
casts and things directly inside arrays
00:19:51.280
for typing the element of an arrays or
00:19:54.080
typing the values in a hash can also
00:19:56.799
apply it to nodes and all the kind of
00:19:59.679
expression we use in Ruby
00:20:02.960
for the things that do not exist in ABS
00:20:04.880
that we still need with so for example
00:20:06.880
declaring a class as abstract we have to
00:20:08.640
find another way because we cannot use
00:20:10.160
the ABS comment itself to represent this
00:20:13.200
because there is no syntax equivalent
00:20:14.720
for it so we just decided to reuse the
00:20:16.640
same kind of aird do annotations I can
00:20:18.880
use a hashtag at abstract on top of my
00:20:20.880
class and I'm going to call generate the
00:20:23.679
equivalent so DSL for this that is
00:20:26.080
calling the abstract bang method same
00:20:28.640
thing for the interface and I'm going to
00:20:30.480
call the uh so with the at at interface
00:20:33.440
I'm going to call the interface bang
00:20:35.400
method finally so has this feature that
00:20:37.840
is requiring an ancestor that means that
00:20:39.440
I can only include the module indexable
00:20:42.880
in a class that descend from a node so
00:20:45.360
like a subclass of a node and for this
00:20:47.760
you need to call this require ancestor
00:20:50.240
method we're representing this again
00:20:51.679
with the at comment uh just on top of
00:20:54.320
the module
00:20:56.159
we can also use this annotations on top
00:20:58.000
of methods for example when you want to
00:20:59.679
define that the method is abstract uh
00:21:02.080
very useful when you're having a large
00:21:03.440
codebase and you want to establish
00:21:04.720
contracts with other teams that are
00:21:06.240
working on the same codebase than you uh
00:21:08.240
in so you represent this by calling
00:21:09.919
abstract inside the s block for us in
00:21:11.919
ABS we're going to do it with the at
00:21:13.760
abstract on top of the
00:21:15.480
method uh we can do the override when
00:21:18.480
you're defining an an abstract method uh
00:21:20.960
we use override in a sig block in so
00:21:23.360
we're going to do that with at override
00:21:25.600
in a comment this is still very
00:21:26.960
experimental but that works uh finally
00:21:30.080
generics again experimental but like you
00:21:32.400
can make it work we decided to use
00:21:35.039
because there is no real way in Ruby to
00:21:37.120
express what is a generic we decided to
00:21:38.799
use the the equivalent of a signature
00:21:40.559
before a class so on top of your class
00:21:42.400
or your module you could do hash colon
00:21:45.039
with in brackets the types that your
00:21:47.360
generic container is taking so in We
00:21:50.559
will do that by creating a constant E
00:21:52.640
that calls type parameter type member
00:21:55.039
and saying like for example here I'm
00:21:56.640
having a E that is my type member that
00:21:58.240
is having an upper bound to node I can
00:22:00.880
represent this with bracket E and um
00:22:03.840
lesser than nodes in my LBS signature
00:22:06.320
and that's going to generate the same
00:22:07.600
code here again this is just what so is
00:22:09.840
this is not what you actually execute at
00:22:12.760
runtime when we create the when we
00:22:15.919
instantiate the class in so because we
00:22:18.240
have this runtime equivalent here um
00:22:21.280
when we're using sober runtime it's
00:22:23.039
going to actually create the bracket
00:22:24.559
method for us on the generic class and
00:22:26.720
we don't have this anymore we don't want
00:22:28.240
to run this anymore so the equivalent
00:22:30.799
for this will be to let to tlet with the
00:22:33.679
comment here to say my tree new is
00:22:35.919
actually a tree of node and I'm happy
00:22:37.520
with this then I can use this type
00:22:40.320
members directly into my onto my methods
00:22:42.880
so for example my show operator here is
00:22:45.360
going to take something that is typed as
00:22:46.799
a he uh and I and type check when I'm
00:22:49.919
calling tree shovel devot new I can make
00:22:52.559
sure that dev do new is actually a
00:22:54.080
subtype of my
00:22:56.360
e finally ins and so you can also define
00:23:00.799
method generics where you have a type
00:23:02.320
member that only exists in the context
00:23:03.919
of a method and here we're doing this
00:23:05.440
with the
00:23:06.520
n where we're going to define okay my
00:23:09.120
method here is taking a par type
00:23:10.720
parameter that is n and it's going to
00:23:12.880
take the class the singleton class of n
00:23:15.120
and finally return an array of n so all
00:23:18.080
that works is My lookup methods ex
00:23:20.159
expect a type and it's going to look
00:23:22.400
inside its own tree if there is like
00:23:24.159
something that matches this type so for
00:23:25.520
example I want to look up in my tree if
00:23:27.120
they are like defaf uh instances and
00:23:29.360
it's going to return an an array of the
00:23:31.120
instances of defaf in so this is much
00:23:33.600
more verbose this is what we're going
00:23:34.880
the equivalent we're going to generate
00:23:36.480
we're calling to type uh parameters
00:23:38.880
we're creating a symbol n is going to
00:23:41.039
represent my type parameter and when I
00:23:42.960
want to reference this reference it
00:23:44.720
inside my signature I have to call uh
00:23:47.200
t.type type parameter with the symbol n
00:23:51.080
here so that's a lot of changes if you
00:23:54.640
need to migrate your codebase from the
00:23:56.720
old so syntax to the RBS comments that's
00:23:59.120
kind of painful to do manually for this
00:24:00.960
we decided to provide uh automated
00:24:03.440
translation tool uh we added this
00:24:05.600
feature to a tool that we own that is
00:24:07.360
called spoom that is doing a bunch of
00:24:08.640
things around static analysis for code
00:24:10.720
uh and also bunch of different so
00:24:12.720
related tooling so if you run spoom sb6
00:24:16.000
translates in your codebase it's going
00:24:17.360
to translate all your old sob syntax
00:24:20.000
signatures into the syntax comment
00:24:23.120
equivalent and we have the same thing
00:24:24.880
for assertion with spoon srb assession
00:24:29.320
translate uh of course big codebase you
00:24:32.400
don't want to be changing all the
00:24:33.840
signatures at the same time that's going
00:24:35.279
to be a hopeful uh awful PR to pull
00:24:37.919
request to review by your teammates so
00:24:39.600
maybe you want to do this in multiple
00:24:40.960
installment installment so for example
00:24:43.279
you could be just migrating file by file
00:24:45.120
and opening pull request for this so I
00:24:47.440
coined this thing that I like to be the
00:24:49.039
gradual gradual typing where you can
00:24:50.799
still use the old service syntax at the
00:24:52.799
same time that you're doing using the
00:24:54.159
ABS comments and everything works
00:24:57.960
properly you may have noticed that we
00:25:00.240
also have syntax highlighting for those
00:25:02.320
comments where we having like this
00:25:03.840
coloration here the void has a certain
00:25:05.760
type and stuff we already shipped that
00:25:08.000
inside a Ruby LSP and it's already
00:25:09.679
providing um the syntax highlighting for
00:25:12.240
RBS comments if you have an LSP that is
00:25:14.480
up to
00:25:15.320
date and what if you really really
00:25:17.520
really don't want type annotations in
00:25:20.400
your code well we actually also provided
00:25:22.880
an option in the Ruby LSP where you can
00:25:24.559
twe tweak the opacity of signatures and
00:25:27.440
type annotations so you can see you can
00:25:29.679
say I want the opacity of my type
00:25:31.360
annotation to zero and it's actually
00:25:33.279
going to make disappear your type
00:25:34.720
annotation so your colleagues can still
00:25:36.400
be happy and you can be happy and
00:25:38.080
everybody is happy and this
00:25:40.679
perfect of course using comments are
00:25:43.440
some drawbacks let's just cover them
00:25:45.120
quickly uh the first one is the type
00:25:47.440
checking time is longer because we need
00:25:49.919
to now pass the comments and we need to
00:25:51.679
pass the RBS that is inside the comments
00:25:54.080
so with the proto version we were like
00:25:56.159
up to two times slower than the vanilla
00:25:59.120
sorbet uh we actually worked on this a
00:26:02.080
lot my colleague KHN has been doing like
00:26:03.919
tremendous improvement to it and we
00:26:05.600
actually now like very close to having
00:26:07.440
no overhead at all uh it's not released
00:26:09.679
yet but very soon we also have plans to
00:26:12.720
migrate the internal parser of Sorbet to
00:26:14.720
Prism to benefit from Prism speed and
00:26:16.720
also we're thinking about maybe
00:26:18.480
migrating the comment passing and
00:26:19.919
comment association to Prism directly to
00:26:21.760
avoid the overhand in
00:26:23.640
so because it's comments we don't have
00:26:26.000
any kind of rubocop support for it yet
00:26:27.919
so no way to enforce spaces commas
00:26:30.720
before after commas and parentheses and
00:26:32.799
stuff um if you want to provide rubocop
00:26:36.240
support for LBS signatures please do so
00:26:38.559
that will be super
00:26:40.440
helpful and the last problem is the last
00:26:43.360
the loss of runtime type checking so
00:26:45.440
everything I explained right now is in
00:26:47.919
the static process when you're running
00:26:49.679
so on your codebase like running sbtc on
00:26:52.559
your codebase this is what so was doing
00:26:54.320
and was seeing and it was using your sig
00:26:57.200
to do the type checking
00:26:59.080
statically but with so because we're
00:27:02.400
actually having those sig artifact in
00:27:04.720
the code you can also enable what is
00:27:06.480
called runtime type checking where
00:27:07.919
you're going to check to check the type
00:27:09.279
of things at runtime while it's running
00:27:11.600
this may not be something you want to
00:27:13.039
run in production because there is some
00:27:14.880
overhead but when you're in development
00:27:17.039
mode or on CI that will be super useful
00:27:19.279
to know that you're casting something to
00:27:21.039
the proper type and you're not trying to
00:27:22.960
lie to the static type checker because
00:27:25.279
we changed the SI calls into comments
00:27:27.360
there is no more artifacts that the
00:27:28.880
runtime type checker could be using so
00:27:31.120
we're losing this runtime type checking
00:27:32.720
and that may be a problem because it's
00:27:34.240
very easy to lie to the type checker and
00:27:36.000
then you actually having a type error
00:27:37.840
that the type checker didn't find we
00:27:40.480
have a way to reenable it using Ruby
00:27:42.400
next uh require hooks that means that at
00:27:44.960
runtime when you're going to execute
00:27:46.559
your code when you're going to load a
00:27:48.400
file require it at require time we're
00:27:51.039
going to rewrite this file in the same
00:27:52.640
way we did inside so when we're going to
00:27:54.640
do it in Ruby to reingject the SIG in
00:27:57.440
your actual executed code disclaimer do
00:28:00.640
not do that in production this is very
00:28:02.720
very not performant but in development
00:28:05.200
mode that may be super useful and so
00:28:07.360
when you're going to run your code if
00:28:09.200
you need it to enable runtime type
00:28:11.120
checking we can rewrite this file and
00:28:12.640
reinject the sig blocks inside your
00:28:16.039
file everything I explained I showed
00:28:19.039
today is already live you can see it by
00:28:21.600
yourself we already started migrating
00:28:23.360
some of the gems that we own so for
00:28:24.799
example the Ruby LSP Tapioa Airbi
00:28:27.520
already use this new RBS syntax and
00:28:29.600
we're in the process of removing the
00:28:30.720
sober runtime dependency from it so you
00:28:32.960
can already see all we went from
00:28:34.559
removing the signatures the annotations
00:28:36.559
translating the annotations and stuff
00:28:38.480
spoiler alert is mostly running a tool
00:28:40.320
to translate it
00:28:41.640
automatically if you want to learn more
00:28:43.600
about it you can also install the last
00:28:45.279
version of Sorb and take a look at the
00:28:47.200
documentation to see what are the
00:28:50.080
different features and it works and get
00:28:51.919
started in your codebase with this I'm
00:28:54.640
going to wish you a happy typing and if
00:28:57.440
you have any question please find me i'm
00:28:59.039
the guy looking like this in the crowd
00:29:01.279
and if you're interested in working in
00:29:02.559
the toolings anything about developer
00:29:05.120
developer experience Ruby and Rails
00:29:06.880
infrastructure please take a look at the
00:29:08.640
QR code here and apply thank you very
00:29:11.279
much