Skip to content

Commit dc130df

Browse files
committed
Add Ruby typechecking with Sorbet and Tapioca
1 parent 20eeeff commit dc130df

File tree

118 files changed

+304879
-806
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+304879
-806
lines changed

Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ gem "pygments.rb"
1616
gem "rake", "~> 13.0"
1717
gem "rouge"
1818
gem "ruby-progressbar", "~> 1.13"
19+
gem 'sorbet-runtime'
1920
gem "treetop", "1.6.12"
2021
gem "ttfunk", "1.7" # needed to avoid having asciidoctor-pdf dependencies pulling in a buggy version of ttunk (1.8)
2122
gem "webrick"
@@ -29,5 +30,7 @@ group :development do
2930
gem "rubocop-minitest"
3031
gem "ruby-prof"
3132
gem "ruby-prof-flamegraph", git: "https://github.com/oozou/ruby-prof-flamegraph.git", ref: "fc3c437", require: false
33+
gem "sorbet"
3234
gem "solargraph"
35+
gem 'tapioca', require: false
3336
end

Gemfile.lock

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ GEM
6565
reline (>= 0.3.8)
6666
diff-lcs (1.6.0)
6767
drb (2.2.1)
68+
erubi (1.13.1)
6869
hana (1.3.7)
6970
hashery (2.1.2)
7071
i18n (1.14.7)
@@ -89,6 +90,7 @@ GEM
8990
logger (1.6.6)
9091
matrix (0.4.2)
9192
minitest (5.25.5)
93+
netrc (0.11.0)
9294
nokogiri (1.18.6-aarch64-linux-gnu)
9395
racc (~> 1.4)
9496
nokogiri (1.18.6-x86_64-linux-gnu)
@@ -125,6 +127,7 @@ GEM
125127
pdf-reader (~> 2.0)
126128
prawn (~> 2.2)
127129
prettyprint (0.2.0)
130+
prism (1.4.0)
128131
psych (5.2.3)
129132
date
130133
stringio
@@ -133,6 +136,10 @@ GEM
133136
racc (1.8.1)
134137
rainbow (3.1.1)
135138
rake (13.2.1)
139+
rbi (0.3.1)
140+
prism (~> 1.0)
141+
rbs (>= 3.4.4)
142+
sorbet-runtime (>= 0.5.9204)
136143
rbs (3.9.1)
137144
logger
138145
rdbg (0.1.0)
@@ -187,7 +194,31 @@ GEM
187194
tilt (~> 2.0)
188195
yard (~> 0.9, >= 0.9.24)
189196
yard-solargraph (~> 0.1)
197+
sorbet (0.5.11966)
198+
sorbet-static (= 0.5.11966)
199+
sorbet-runtime (0.5.11966)
200+
sorbet-static (0.5.11966-aarch64-linux)
201+
sorbet-static (0.5.11966-x86_64-linux)
202+
sorbet-static-and-runtime (0.5.11966)
203+
sorbet (= 0.5.11966)
204+
sorbet-runtime (= 0.5.11966)
205+
spoom (1.6.1)
206+
erubi (>= 1.10.0)
207+
prism (>= 0.28.0)
208+
rbi (>= 0.2.3)
209+
sorbet-static-and-runtime (>= 0.5.10187)
210+
thor (>= 0.19.2)
190211
stringio (3.1.5)
212+
tapioca (0.16.11)
213+
benchmark
214+
bundler (>= 2.2.25)
215+
netrc (>= 0.11.0)
216+
parallel (>= 1.21.0)
217+
rbi (~> 0.2)
218+
sorbet-static-and-runtime (>= 0.5.11087)
219+
spoom (>= 1.2.0)
220+
thor (>= 1.2.0)
221+
yard-sorbet
191222
thor (1.3.2)
192223
tilt (2.6.0)
193224
treetop (1.6.12)
@@ -203,6 +234,9 @@ GEM
203234
yard (0.9.37)
204235
yard-solargraph (0.1.0)
205236
yard (~> 0.9)
237+
yard-sorbet (0.9.0)
238+
sorbet-runtime
239+
yard
206240

207241
PLATFORMS
208242
aarch64-linux-gnu
@@ -229,6 +263,9 @@ DEPENDENCIES
229263
ruby-prof-flamegraph!
230264
ruby-progressbar (~> 1.13)
231265
solargraph
266+
sorbet
267+
sorbet-runtime
268+
tapioca
232269
treetop (= 1.6.12)
233270
ttfunk (= 1.7)
234271
webrick

Rakefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ namespace :test do
162162
t.process_env
163163
ruby t.make_test_cmd
164164
end
165+
166+
desc "Type-check the Ruby library"
167+
task :sorbet do
168+
sh "srb tc --no-config @.sorbet-config"
169+
end
165170
end
166171

167172
desc "Clean up all generated files"
@@ -409,6 +414,7 @@ namespace :test do
409414
task :smoke do
410415
Rake::Task["test:idl_compiler"].invoke
411416
Rake::Task["test:lib"].invoke
417+
Rake::Task["test:sorbet"].invoke
412418
Rake::Task["test:schema"].invoke
413419
Rake::Task["test:idl"].invoke
414420
Rake::Task["test:inst_encodings"].invoke

bin/tapioca

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
3+
ROOT=$(dirname $(realpath ${BASH_SOURCE[0]}))
4+
source $ROOT/setup
5+
6+
$BUNDLE exec tapioca "$@"

cfgs/config_validation.rb

Lines changed: 0 additions & 93 deletions
This file was deleted.

lib/arch_obj_models/csr.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# frozen_string_literal: true
2+
# typed: true
23

34
require_relative "obj"
45

@@ -176,7 +177,7 @@ def length(effective_xlen = nil)
176177
when Integer
177178
@data["length"]
178179
else
179-
raise "Unexpected length field for #{csr.name}"
180+
raise "Unexpected length field for #{name}"
180181
end
181182
end
182183

@@ -210,7 +211,7 @@ def max_length
210211
when Integer
211212
@data["length"]
212213
else
213-
raise "Unexpected length field for #{csr.name}"
214+
raise "Unexpected length field for #{name}"
214215
end
215216
end
216217

@@ -306,6 +307,7 @@ def possible_fields_for(effective_xlen)
306307

307308
# @return [Array<CsrField>] All implemented fields for this CSR
308309
# Excluded any fields that are defined by unimplemented extensions
310+
sig {returns(T::Array[CsrField])}
309311
def possible_fields
310312
@possible_fields ||= fields.select do |f|
311313
f.exists_in_cfg?(cfg_arch)
@@ -321,6 +323,7 @@ def fields
321323

322324
# @return [Array<CsrField>] All known fields of this CSR when XLEN == +effective_xlen+
323325
# equivalent to {#fields} if +effective_xlen+ is nil
326+
sig {params(effective_xlen: T.nilable(Integer)).returns(T::Array[CsrField])}
324327
def fields_for(effective_xlen)
325328
fields.select { |f| effective_xlen.nil? || !f.key?("base") || f.base == effective_xlen }
326329
end
@@ -518,7 +521,7 @@ def wavedrom_desc(cfg_arch, effective_xlen, exclude_unimplemented: false, option
518521
else
519522
desc["reg"] << { "bits" => field.location(effective_xlen).size, "name" => field.name, type: 3 }
520523
end
521-
last_idx = field.location(effective_xlen).max
524+
last_idx = T.cast(field.location(effective_xlen).max, Integer)
522525
end
523526
if !field_list.empty? && (field_list.last.location(effective_xlen).max != (length(effective_xlen) - 1))
524527
# reserved space at the end

lib/arch_obj_models/csr_field.rb

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
# frozen_string_literal: true
2+
# typed: true
3+
4+
require "sorbet-runtime"
25

36
require_relative "obj"
47

58
require_relative "../idl/passes/gen_option_adoc"
69

710
# A CSR field object
811
class CsrField < DatabaseObject
12+
extend T::Sig;
13+
914
# @return [Csr] The Csr that defines this field
1015
attr_reader :parent
1116

@@ -67,7 +72,7 @@ def optional_in_cfg?(cfg_arch)
6772
# This does not take the parent CSR into account, i.e., a field can be unaffected
6873
# by ext_ver even if the parent CSR is affected
6974
def affected_by?(ext_ver)
70-
@data["definedBy"].nil? ? false : defined_by_condition.possibly_satisfied_by?(version)
75+
@data["definedBy"].nil? ? false : defined_by_condition.possibly_satisfied_by?(ext_ver)
7176
end
7277

7378
# @return [Idl::FunctionBodyAst] Abstract syntax tree of the type() function
@@ -168,6 +173,7 @@ def type(effective_xlen = nil)
168173
@type ||= { 32 => nil, 64 => nil }
169174
return @type[effective_xlen] unless @type[effective_xlen].nil?
170175

176+
type = T.let(nil, T.untyped)
171177
type =
172178
if @data.key?("type")
173179
@data["type"]
@@ -223,7 +229,7 @@ def type(effective_xlen = nil)
223229
def type_pretty(effective_xlen = nil)
224230
raise ArgumentError, "Expecting Integer" unless effective_xlen.nil? || effective_xlen.is_a?(Integer)
225231

226-
str = nil
232+
str = T.let(nil, T.nilable(String))
227233
value_result = Idl::AstNode.value_try do
228234
str = type(effective_xlen)
229235
end
@@ -241,7 +247,7 @@ def alias
241247
if @data.key?("alias")
242248
raise "Can't parse alias" unless data["alias"] =~ /^[a-z][a-z0-9]+\.[A-Z0-9]+(\[([0-9]+)(:[0-9]+)?\])?$/
243249

244-
csr_name = Regexp.last_match(1)
250+
csr_name = T.must(Regexp.last_match(1))
245251
csr_field = Regexp.last_match(2)
246252
range = Regexp.last_match(3)
247253
range_start = Regexp.last_match(4)
@@ -250,7 +256,7 @@ def alias
250256
csr_field = cfg_arch.csr(csr_name).field(csr_field)
251257
range =
252258
if range.nil?
253-
field.location
259+
csr_field.location
254260
elsif range_end.nil?
255261
(range_start.to_i..range_start.to_i)
256262
else
@@ -374,7 +380,7 @@ def reset_value
374380
else
375381
ast = pruned_reset_value_ast
376382
symtab = fill_symtab_for_reset(ast)
377-
val = nil
383+
val = T.let(nil, T.untyped)
378384
value_result = Idl::AstNode.value_try do
379385
val = ast.return_value(symtab)
380386
end
@@ -398,7 +404,7 @@ def dynamic_reset_value?
398404
end
399405

400406
def reset_value_pretty
401-
str = nil
407+
str = T.let(nil, T.nilable(String))
402408
value_result = Idl::AstNode.value_try do
403409
str = reset_value
404410
end
@@ -591,6 +597,7 @@ def pruned_sw_write_ast(effective_xlen)
591597
# @param cfg_arch [ConfiguredArchitecture] A config. May be nil if the location is not configturation-dependent
592598
# @param effective_xlen [Integer] The effective xlen, needed since some fields change location with XLEN. If the field location is not determined by XLEN, then this parameter can be nil
593599
# @return [Range] the location within the CSR as a range (single bit fields will be a range of size 1)
600+
sig { params(effective_xlen: T.nilable(Integer)).returns(T::Range[Integer]) }
594601
def location(effective_xlen = nil)
595602
key =
596603
if @data.key?("location")
@@ -615,7 +622,9 @@ def location(effective_xlen = nil)
615622
end
616623

617624
@data[key]..@data[key]
618-
elsif @data[key].is_a?(String)
625+
else
626+
raise "Unexpected location field" unless @data[key].is_a?(String)
627+
619628
e, s = @data[key].split("-").map(&:to_i)
620629
raise "Invalid location" if s > e
621630

lib/arch_obj_models/extension.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require_relative "obj"
44
require_relative "schema"
5+
require_relative "req_expression"
56
require_relative "../version"
67

78
# A parameter (AKA option, AKA implementation-defined value) supported by an extension

0 commit comments

Comments
 (0)