Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
B
brew
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to JiHu GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
KMSCAKKSCFKA AKFACAMADCAS
brew
Commits
ced0da15
Unverified
Commit
ced0da15
authored
4 years ago
by
Markus Reiter
Committed by
GitHub
4 years ago
Browse files
Options
Downloads
Plain Diff
Merge pull request #9129 from reitermarkus/tc-docs
Improve type checking documentation.
parents
baac12b0
3e63acad
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
.github/PULL_REQUEST_TEMPLATE.md
+1
-0
1 addition, 0 deletions
.github/PULL_REQUEST_TEMPLATE.md
Library/Homebrew/dev-cmd/typecheck.rb
+6
-1
6 additions, 1 deletion
Library/Homebrew/dev-cmd/typecheck.rb
docs/Typechecking.md
+112
-68
112 additions, 68 deletions
docs/Typechecking.md
with
119 additions
and
69 deletions
.github/PULL_REQUEST_TEMPLATE.md
+
1
−
0
View file @
ced0da15
...
...
@@ -3,6 +3,7 @@
-
[ ] Have you added an explanation of what your changes do and why you'd like us to include them?
-
[
] Have you written new tests for your changes? [Here's an example
](
https://github.com/Homebrew/brew/blob/HEAD/Library/Homebrew/test/PATH_spec.rb
)
.
-
[ ] Have you successfully run
`brew style`
with your changes locally?
-
[ ] Have you successfully run
`brew typecheck`
with your changes locally?
-
[ ] Have you successfully run
`brew tests`
with your changes locally?
-
[ ] Have you successfully run
`brew man`
locally and committed any changes?
...
...
This diff is collapsed.
Click to expand it.
Library/Homebrew/dev-cmd/typecheck.rb
+
6
−
1
View file @
ced0da15
...
...
@@ -107,7 +107,12 @@ module Homebrew
srb_exec
+=
[
"--file"
,
"../
#{
args
.
file
}
"
]
if
args
.
file
srb_exec
+=
[
"--dir"
,
"../
#{
args
.
dir
}
"
]
if
args
.
dir
end
Homebrew
.
failed
=
!
system
(
*
srb_exec
)
success
=
system
(
*
srb_exec
)
return
if
success
$stderr
.
puts
"Check
#{
Formatter
.
url
(
"https://docs.brew.sh/Typechecking"
)
}
for "
\
"more information on how to resolve these errors."
Homebrew
.
failed
=
true
end
end
end
This diff is collapsed.
Click to expand it.
docs/Typechecking.md
+
112
−
68
View file @
ced0da15
# Type Checking With Sorbet
The majority of the code in Homebrew is written in Ruby which is a dynamic
language. To avail the benefits of static type checking, we have set up Sorbet in
our codebase which provides the benefits of static type checking to dynamic languages
like Ruby.
<br>
[
Sorbet's Documentation
](
https://sorbet.org/docs/overview
)
is a
good place to get started if you want to dive deeper into Sorbet and it's abilities.
## Sorbet elements in the Homebrew Codebase
The
[
`sorbet/`
](
https://github.com/Homebrew/brew/tree/master/Library/Homebrew/sorbet
)
directory in
`Library/Homebrew`
consists of:
-
The
`rbi/`
directory. It contains all Ruby Interface files, which help Sorbet to
learn about constants, ancestors, and methods defined in ways it doesn’t understand
natively. RBI files for all gems are auto-generated using
[
Tapioca
](
https://github.com/Shopify/tapioca#tapioca
)
. We can also create a RBI
file to help Sorbet understand dynamic definitions.
For example: Sorbet assumes that
`Kernel`
is not necessarily included in our modules
and classes, hence we use RBI files to explicitly include the Kernel Module. Here is an
[
example
](
https://github.com/Homebrew/brew/blob/72419630b4658da31556a0f6ef1dfa633cf4fe4f/Library/Homebrew/sorbet/rbi/homebrew.rbi#L3-L5
)
in our codebase.
-
The
`config`
file. It is actually a newline-separated list of arguments to pass to
`srb tc`
, the same as if they’d been passed at the command line. Arguments in the config
file are always passed first (if it exists), followed by arguments provided on the
command line. We use it ignore the
`Library/Homebrew/vendor`
directory, which
contains gem definitions which we do not wish to type check.
-
Every Ruby file in the codebase is divided into three strictness levels: false,
true and strict. The
`false`
files only
report errors related to the syntax, constant resolution and correctness of the
method signatures, and not type errors. We use this file to override strictness
on a file-by-file basis. Our longtime goal is to move all
`false`
files to
`true`
and start reporting type errors on those files as well. If you are making changes
that require adding a new ruby file, we would urge you to add it to
`true`
and work
out the resulting type errors. Read more about Sorbet's strictness levels
[
here
](
https://sorbet.org/docs/static#file-level-granularity-strictness-levels
)
.
language. To avail the benefits of static type checking, we have set up
Sorbet in our codebase which provides the benefits of static type checking
to dynamic languages like Ruby.
The [Sorbet Documentation] is a good place
to get started if you want to dive deeper into Sorbet and it's abilities.
## Sorbet in the Homebrew Codebase
### Inline Type Annotations
To add type annotations to a class or module, we need to first extend it with
the
`T::Sig`
module (read this as
`Type::Signature`
). This adds the
`sig`
method which is used to annotate method signatures. Here's a simple example:
```
ruby
class
MyClass
extend
T
::
Sig
sig
{
params
(
name:
String
).
returns
(
String
)
}
def
my_method
(
name
)
"Hello,
#{
name
}
!"
end
end
```
With
`params`
, we specify that we have a parameter
`name`
which must be a
`String`
and with
`returns`
, we specify that this method always returns
a
`String`
.
For more information on how to express more complex types, refer to the
official documentation:
-
[
Method Signatures
](
https://sorbet.org/docs/sigs
)
-
[
Class Types
](
https://sorbet.org/docs/class-types
)
-
[
Nilable Types
](
https://sorbet.org/docs/nilable-types
)
-
[
Union Types
](
https://sorbet.org/docs/union-types
)
### Ruby Interface Files (`.rbi`)
RBI files help Sorbet learn about constants, ancestors and methods
defined in ways it doesn’t understand natively. We can also create a
RBI file to help Sorbet understand dynamic definitions.
Sometimes it is necessary to explicitly include the
`Kernel`
module in
order for Sorbet to know that methods such as
`puts`
are available in
a given context. This is mostly necessary for modules since they can
be used in both
`BasicObject`
s (which don't include
`Kernel`
) and
`Object`
s (which include
`Kernel`
by default). In this case, it is
necessary to create an
`.rbi`
file ([example]) since re-including the
`Kernel`
module in actual code can break things.
Read more about RBI files
[
here
](
https://sorbet.org/docs/rbi
)
.
[
example
]:
https://github.com/Homebrew/brew/blob/61b79318ed089b5010501e2cbf163fd8e48e2dfc/Library/Homebrew/global.rbi
### The [`Library/Homebrew/sorbet`] Directory
[
`Library/Homebrew/sorbet`
]:
https://github.com/Homebrew/brew/tree/master/Library/Homebrew/sorbet
-
The
`rbi`
directory contains all Ruby Interface (
`.rbi`
) files
auto-generated by running
`brew typecheck --update`
:
-
RBI files for all gems are generated using
[
Tapioca
](
https://github.com/Shopify/tapioca#tapioca
)
.
-
Definitions for dynamic code (i.e. meta-programming) are generated using
`srb rbi hidden-definitions`
.
-
Definitions for missing constants are generated using
`srb rbi todo`
.
-
The
`config`
file is a newline-separated list of arguments to pass to
`srb tc`
, the same as if they’d been passed at the command-line. Arguments
in the config file are always passed first, followed by arguments provided
on the command-line. We use it to ignore Gem directories which we do not
wish to type check.
-
Every Ruby file in the codebase has a magic
`# typed: <level>`
comment at the
top, where
`<level>`
is one of [Sorbet's strictness levels], usually
`false`
,
`true`
or
`strict`
. The
`false`
files only report errors related to the
syntax, constant resolution and correctness of the method signatures, but no
type errors. Our long-term goal is to move all
`false`
files to
`true`
and
start reporting type errors on those files as well. Therefore, when adding
new files, you should ideally mark it with
`# typed: true`
and work out any
resulting type errors.
[
Sorbet's strictness levels
]:
https://sorbet.org/docs/static#file-level-granularity-strictness-levels
## Using `brew typecheck`
When run without any arguments,
`brew typecheck`
, will run considering the strictness levels
set in each of the individual Ruby files in the core Homebrew codebase. However, when
t
ypecheck
is run on a specific file or directory, more errors may show up since Sorbet
i
t is run on a specific file or directory, more errors may show up since Sorbet
cannot resolve constants defined outside the scope of the specified file. These
problems can be solved with RBI files. Currently
`brew typecheck`
provides
`--quiet`
,
`--file`
,
`--dir`
and
`--ignore`
options but you can explore more options with
`srb tc --help`
and
...
...
@@ -51,35 +101,29 @@ passing them with `srb tc`.
Sorbet reports type errors along with an error reference code, which can be used
to look up more information on how to debug the error, or what causes the error in
the Sorbet documentation. Here is how we debug some common type errors:
*
Using
`T.reveal_type`
. In files which are
`true`
or higher, if we wrap a variable
or method call in
`T.reveal_type`
, Sorbet will show us what type it thinks that
variable has in the output of
`srb tc`
. This is particularly useful when writing
[
method signatures
](
https://sorbet.org/docs/sigs
)
and debugging. Make sure to
remove this line from your code before committing your changes, since this is
just a debugging tool.
*
One of the most frequent errors that we've encountered is:
`7003: Method does not exist.`
Since Ruby is a very dynamic language, methods can be defined in ways Sorbet cannot
see statically. In such cases, check if the method exists at runtime, if not, then
Sorbet has caught a future bug! But, it is also possible that even though a method
exists at runtime, Sorbet cannot see it. In such cases, we use
`*.rbi`
files.
Read more about RBI files
[
here
](
https://sorbet.org/docs/rbi
)
.
*
Since Sorbet does not automatically assume that Kernel is to be included in Modules,
we may encounter many errors while trying to use methods like
`puts`
,
`ohai`
,
`odebug`
et cetera.
A simple workaround for this would be to add an extra
`include Kernel`
line in the
respective RBI file.
*
The tips above are very generic and apply to lots of cases. For some common gotchas
when using Sorbet, refer to the
[
Sorbet Error Reference
](
https://sorbet.org/docs/error-reference
)
and
[
FAQ
](
https://sorbet.org/docs/faq
)
.
## Method Signatures
Detailed explanation about why we use Method Signatures and its syntax can be found
[
here
](
https://sorbet.org/docs/sigs
)
. The only extra thing to keep in mind is that
we add method signatures to RBI files instead of the actual method definition in
the code. This way we preserve the original code structure and everything related to
Sorbet is kept within the
`Library/Homebrew/sorbet`
directory.
the [Sorbet Documentation]. Here is how to debug some common type errors:
-
Using
`T.reveal_type`
. In files which are
`true`
or higher, if we wrap a variable
or method call in
`T.reveal_type`
, Sorbet will show us what type it thinks that
variable has in the output of
`srb tc`
. This is particularly useful when writing
[
method signatures
](
https://sorbet.org/docs/sigs
)
and debugging. Make sure to
remove this line from your code before committing your changes, since this is
just a debugging tool.
-
One of the most frequent errors that we've encountered is:
`7003: Method does not exist.`
Since Ruby is a very dynamic language, methods can be defined in ways Sorbet cannot
see statically. In such cases, check if the method exists at runtime, if not, then
Sorbet has caught a future bug! But, it is also possible that even though a method
exists at runtime, Sorbet cannot see it. In such cases, we use
[
`.rbi` files
](
#ruby-interface-files-rbi
)
.
-
Since Sorbet does not automatically assume that Kernel is to be included in Modules,
we may encounter many errors while trying to use methods like
`puts`
,
`ohai`
,
`odebug`
et cetera.
A simple workaround for this would be to add an extra
`include Kernel`
line in the
respective RBI file.
-
The tips above are very generic and apply to lots of cases. For some common gotchas
when using Sorbet, refer to the
[
Sorbet Error Reference
](
https://sorbet.org/docs/error-reference
)
and
[
FAQ
](
https://sorbet.org/docs/faq
)
.
[
Sorbet Documentation
]:
https://sorbet.org/docs/overview
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment