steep 0.44.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +8 -0
  3. data/.github/workflows/ruby.yml +3 -2
  4. data/.gitignore +0 -1
  5. data/CHANGELOG.md +42 -0
  6. data/Gemfile +0 -3
  7. data/Gemfile.lock +75 -0
  8. data/README.md +2 -1
  9. data/lib/steep/annotation_parser.rb +1 -1
  10. data/lib/steep/ast/builtin.rb +7 -1
  11. data/lib/steep/ast/types/factory.rb +19 -25
  12. data/lib/steep/cli.rb +7 -1
  13. data/lib/steep/diagnostic/lsp_formatter.rb +59 -6
  14. data/lib/steep/diagnostic/ruby.rb +188 -60
  15. data/lib/steep/diagnostic/signature.rb +38 -15
  16. data/lib/steep/drivers/check.rb +3 -0
  17. data/lib/steep/drivers/init.rb +10 -3
  18. data/lib/steep/drivers/utils/driver_helper.rb +15 -0
  19. data/lib/steep/drivers/validate.rb +1 -1
  20. data/lib/steep/drivers/watch.rb +3 -0
  21. data/lib/steep/equatable.rb +21 -0
  22. data/lib/steep/interface/function.rb +798 -579
  23. data/lib/steep/project/dsl.rb +135 -36
  24. data/lib/steep/project/options.rb +13 -53
  25. data/lib/steep/project/target.rb +22 -8
  26. data/lib/steep/server/interaction_worker.rb +245 -26
  27. data/lib/steep/server/master.rb +2 -2
  28. data/lib/steep/server/type_check_worker.rb +6 -9
  29. data/lib/steep/services/file_loader.rb +26 -19
  30. data/lib/steep/services/goto_service.rb +1 -0
  31. data/lib/steep/services/hover_content.rb +135 -80
  32. data/lib/steep/source.rb +12 -11
  33. data/lib/steep/type_construction.rb +435 -502
  34. data/lib/steep/type_inference/block_params.rb +3 -6
  35. data/lib/steep/type_inference/method_params.rb +483 -0
  36. data/lib/steep/type_inference/send_args.rb +599 -128
  37. data/lib/steep/typing.rb +46 -21
  38. data/lib/steep/version.rb +1 -1
  39. data/lib/steep.rb +4 -2
  40. data/sample/Steepfile +10 -3
  41. data/smoke/alias/Steepfile +2 -1
  42. data/smoke/and/Steepfile +2 -1
  43. data/smoke/array/Steepfile +2 -1
  44. data/smoke/array/test_expectations.yml +3 -3
  45. data/smoke/block/Steepfile +2 -2
  46. data/smoke/block/c.rb +0 -1
  47. data/smoke/case/Steepfile +2 -1
  48. data/smoke/class/Steepfile +2 -1
  49. data/smoke/class/test_expectations.yml +12 -15
  50. data/smoke/const/Steepfile +2 -1
  51. data/smoke/diagnostics/Steepfile +2 -1
  52. data/smoke/diagnostics/different_method_parameter_kind.rb +9 -0
  53. data/smoke/diagnostics/method_arity_mismatch.rb +2 -2
  54. data/smoke/diagnostics/method_parameter_mismatch.rb +10 -0
  55. data/smoke/diagnostics/test_expectations.yml +108 -31
  56. data/smoke/diagnostics-rbs/Steepfile +1 -1
  57. data/smoke/diagnostics-rbs/mixin-class-error.rbs +6 -0
  58. data/smoke/diagnostics-rbs/test_expectations.yml +12 -0
  59. data/smoke/diagnostics-rbs-duplicated/Steepfile +2 -1
  60. data/smoke/diagnostics-ruby-unsat/Steepfile +2 -1
  61. data/smoke/dstr/Steepfile +2 -1
  62. data/smoke/ensure/Steepfile +2 -1
  63. data/smoke/ensure/test_expectations.yml +3 -3
  64. data/smoke/enumerator/Steepfile +2 -1
  65. data/smoke/enumerator/test_expectations.yml +1 -1
  66. data/smoke/extension/Steepfile +2 -1
  67. data/smoke/extension/e.rbs +1 -1
  68. data/smoke/hash/Steepfile +2 -1
  69. data/smoke/hello/Steepfile +2 -1
  70. data/smoke/if/Steepfile +2 -1
  71. data/smoke/implements/Steepfile +2 -1
  72. data/smoke/initialize/Steepfile +2 -1
  73. data/smoke/integer/Steepfile +2 -1
  74. data/smoke/interface/Steepfile +2 -1
  75. data/smoke/kwbegin/Steepfile +2 -1
  76. data/smoke/lambda/Steepfile +2 -1
  77. data/smoke/literal/Steepfile +2 -1
  78. data/smoke/literal/test_expectations.yml +2 -2
  79. data/smoke/map/Steepfile +2 -1
  80. data/smoke/method/Steepfile +2 -1
  81. data/smoke/method/test_expectations.yml +11 -10
  82. data/smoke/module/Steepfile +2 -1
  83. data/smoke/regexp/Steepfile +2 -1
  84. data/smoke/regression/Steepfile +2 -1
  85. data/smoke/rescue/Steepfile +2 -1
  86. data/smoke/rescue/test_expectations.yml +3 -3
  87. data/smoke/self/Steepfile +2 -1
  88. data/smoke/skip/Steepfile +2 -1
  89. data/smoke/stdout/Steepfile +2 -1
  90. data/smoke/super/Steepfile +2 -1
  91. data/smoke/toplevel/Steepfile +2 -1
  92. data/smoke/toplevel/test_expectations.yml +3 -3
  93. data/smoke/tsort/Steepfile +4 -5
  94. data/smoke/tsort/test_expectations.yml +2 -2
  95. data/smoke/type_case/Steepfile +2 -1
  96. data/smoke/unexpected/Steepfile +2 -1
  97. data/smoke/yield/Steepfile +2 -1
  98. data/steep.gemspec +2 -2
  99. metadata +16 -10
  100. data/sig/project.rbi +0 -109
@@ -7,21 +7,34 @@ module Steep
7
7
  attr_reader :libraries
8
8
  attr_reader :signatures
9
9
  attr_reader :ignored_sources
10
- attr_reader :vendor_dir
11
- attr_reader :strictness_level
12
- attr_reader :typing_option_hash
10
+ attr_reader :stdlib_root
11
+ attr_reader :core_root
13
12
  attr_reader :repo_paths
13
+ attr_reader :code_diagnostics_config
14
+ attr_reader :project
15
+ attr_reader :collection_config_path
14
16
 
15
- def initialize(name, sources: [], libraries: [], signatures: [], ignored_sources: [], repo_paths: [])
17
+ NONE = Object.new.freeze
18
+
19
+ def initialize(name, sources: [], libraries: [], signatures: [], ignored_sources: [], repo_paths: [], code_diagnostics_config: {}, project: nil, collection_config_path: NONE)
16
20
  @name = name
17
21
  @sources = sources
18
22
  @libraries = libraries
19
23
  @signatures = signatures
20
24
  @ignored_sources = ignored_sources
21
- @vendor_dir = nil
22
- @strictness_level = :default
23
- @typing_option_hash = {}
25
+ @core_root = nil
26
+ @stdlib_root = nil
24
27
  @repo_paths = []
28
+ @code_diagnostics_config = code_diagnostics_config
29
+ @project = project
30
+ @collection_config_path =
31
+ case collection_config_path
32
+ when NONE
33
+ path = project&.absolute_path(RBS::Collection::Config::PATH)
34
+ path&.exist? ? path : nil
35
+ else
36
+ collection_config_path
37
+ end
25
38
  end
26
39
 
27
40
  def initialize_copy(other)
@@ -30,10 +43,12 @@ module Steep
30
43
  @libraries = other.libraries.dup
31
44
  @signatures = other.signatures.dup
32
45
  @ignored_sources = other.ignored_sources.dup
33
- @vendor_dir = other.vendor_dir
34
- @strictness_level = other.strictness_level
35
- @typing_option_hash = other.typing_option_hash
36
46
  @repo_paths = other.repo_paths.dup
47
+ @core_root = other.core_root
48
+ @stdlib_root = other.stdlib_root
49
+ @code_diagnostics_config = other.code_diagnostics_config.dup
50
+ @project = other.project
51
+ @collection_config_path = other.collection_config_path
37
52
  end
38
53
 
39
54
  def check(*args)
@@ -48,40 +63,122 @@ module Steep
48
63
  libraries.push(*args)
49
64
  end
50
65
 
51
- def typing_options(level = @strictness_level, **hash)
52
- @strictness_level = level
53
- @typing_option_hash = hash
66
+ def typing_options(level = nil, **hash)
67
+ Steep.logger.error "#typing_options is deprecated and has no effect as of version 0.46.0. Update your Steepfile as follows for (almost) equivalent setting:"
68
+
69
+ messages = []
70
+
71
+ messages << "# D = Steep::Diagnostic # Define a constant to shorten namespace"
72
+
73
+ case level
74
+ when :strict
75
+ messages << "configure_code_diagnostics(D::Ruby.strict) # :strict"
76
+ when :default
77
+ messages << "configure_code_diagnostics(D::Ruby.default) # :default"
78
+ when :lenient
79
+ messages << "configure_code_diagnostics(D::Ruby.lenient) # :lenient"
80
+ end
81
+
82
+ messages.each do |msg|
83
+ Steep.logger.error " #{msg}"
84
+ end
85
+
86
+ config = []
87
+
88
+ if hash[:allow_missing_definitions]
89
+ config << "hash[D::Ruby::MethodDefinitionMissing] = nil # allow_missing_definitions"
90
+ end
91
+
92
+ if hash[:allow_fallback_any]
93
+ config << "hash[D::Ruby::FallbackAny] = nil # allow_fallback_any"
94
+ end
95
+
96
+ if hash[:allow_unknown_constant_assignment]
97
+ config << "hash[D::Ruby::UnknownConstantAssigned] = nil # allow_unknown_constant_assignment"
98
+ end
99
+
100
+ if hash[:allow_unknown_method_calls]
101
+ config << "hash[D::Ruby::NoMethod] = nil # allow_unknown_method_calls"
102
+ end
103
+
104
+ unless config.empty?
105
+ Steep.logger.error " configure_code_diagnostics do |hash|"
106
+ config.each do |c|
107
+ Steep.logger.error " #{c}"
108
+ end
109
+ Steep.logger.error " end"
110
+ end
111
+
54
112
  end
55
113
 
56
114
  def signature(*args)
57
115
  signatures.push(*args)
58
116
  end
59
117
 
60
- def update(name: self.name, sources: self.sources, libraries: self.libraries, ignored_sources: self.ignored_sources, signatures: self.signatures)
118
+ def update(name: self.name, sources: self.sources, libraries: self.libraries, ignored_sources: self.ignored_sources, signatures: self.signatures, project: self.project)
61
119
  self.class.new(
62
120
  name,
63
121
  sources: sources,
64
122
  libraries: libraries,
65
123
  signatures: signatures,
66
- ignored_sources: ignored_sources
124
+ ignored_sources: ignored_sources,
125
+ project: project,
67
126
  )
68
127
  end
69
128
 
70
129
  def no_builtin!(value = true)
71
- Steep.logger.error "`no_builtin!` in Steepfile is deprecated and ignored. Use `vendor` instead."
130
+ Steep.logger.error "`#no_builtin!` in Steepfile is deprecated and ignored. Use `#stdlib_path` instead."
72
131
  end
73
132
 
74
133
  def vendor(dir = "vendor/sigs", stdlib: nil, gems: nil)
75
- if stdlib || gems
76
- Steep.logger.warn { "#vendor with stdlib: or gems: keyword is deprecated." }
77
- end
134
+ Steep.logger.error "`#vendor` in Steepfile is deprecated and ignored. Use `#stdlib_path` instead."
135
+ end
78
136
 
79
- @vendor_dir = Pathname(dir)
137
+ def stdlib_path(core_root:, stdlib_root:)
138
+ @core_root = core_root ? Pathname(core_root) : core_root
139
+ @stdlib_root = stdlib_root ? Pathname(stdlib_root) : stdlib_root
80
140
  end
81
141
 
82
142
  def repo_path(*paths)
83
143
  @repo_paths.push(*paths.map {|s| Pathname(s) })
84
144
  end
145
+
146
+ # Configure the code diagnostics printing setup.
147
+ #
148
+ # Yields a hash, and the update the hash in the block.
149
+ #
150
+ # ```rb
151
+ # D = Steep::Diagnostic
152
+ #
153
+ # configure_code_diagnostics do |hash|
154
+ # # Assign one of :error, :warning, :information, :hint or :nil to error classes.
155
+ # hash[D::Ruby::UnexpectedPositionalArgument] = :error
156
+ # end
157
+ # ```
158
+ #
159
+ # Passing a hash is also allowed.
160
+ #
161
+ # ```rb
162
+ # D = Steep::Diagnostic
163
+ #
164
+ # configure_code_diagnostics(D::Ruby.lenient)
165
+ # ```
166
+ #
167
+ def configure_code_diagnostics(hash = nil)
168
+ if hash
169
+ code_diagnostics_config.merge!(hash)
170
+ end
171
+
172
+ yield code_diagnostics_config if block_given?
173
+ end
174
+
175
+ def collection_config(path)
176
+ @collection_config_path = project.absolute_path(path)
177
+ end
178
+
179
+ def disable_collection
180
+ @collection_config_path = nil
181
+ end
85
182
  end
86
183
 
87
184
  attr_reader :project
@@ -107,40 +204,42 @@ module Steep
107
204
  end
108
205
 
109
206
  def self.parse(project, code, filename: "Steepfile")
110
- self.new(project: project).instance_eval(code, filename)
207
+ Steep.logger.tagged filename do
208
+ self.new(project: project).instance_eval(code, filename)
209
+ end
111
210
  end
112
211
 
113
212
  def target(name, template: nil, &block)
114
213
  target = if template
115
- self.class.templates[template]&.dup&.update(name: name) or
214
+ self.class.templates[template]&.dup&.update(name: name, project: project) or
116
215
  raise "Unknown template: #{template}, available templates: #{@@templates.keys.join(", ")}"
117
216
  else
118
- TargetDSL.new(name)
217
+ TargetDSL.new(name, code_diagnostics_config: Diagnostic::Ruby.default.dup, project: project)
119
218
  end
120
219
 
121
- target.instance_eval(&block) if block_given?
220
+ Steep.logger.tagged "target=#{name}" do
221
+ target.instance_eval(&block) if block_given?
222
+ end
122
223
 
123
224
  source_pattern = Pattern.new(patterns: target.sources, ignores: target.ignored_sources, ext: ".rb")
124
225
  signature_pattern = Pattern.new(patterns: target.signatures, ext: ".rbs")
125
226
 
227
+ collection_lock = target.collection_config_path&.then { |p| RBS::Collection::Config.lockfile_of(p) }
228
+
126
229
  Project::Target.new(
127
230
  name: target.name,
128
231
  source_pattern: source_pattern,
129
232
  signature_pattern: signature_pattern,
130
233
  options: Options.new.tap do |options|
131
234
  options.libraries.push(*target.libraries)
132
- options.repository_paths.push(*target.repo_paths)
133
- options.vendor_path = target.vendor_dir
134
-
135
- case target.strictness_level
136
- when :strict
137
- options.apply_strict_typing_options!
138
- when :lenient
139
- options.apply_lenient_typing_options!
140
- end
141
-
142
- options.merge!(target.typing_option_hash)
143
- end
235
+ options.paths = Options::PathOptions.new(
236
+ core_root: target.core_root,
237
+ stdlib_root: target.stdlib_root,
238
+ repo_paths: target.repo_paths
239
+ )
240
+ options.collection_lock = collection_lock
241
+ end,
242
+ code_diagnostics_config: target.code_diagnostics_config
144
243
  ).tap do |target|
145
244
  project.targets << target
146
245
  end
@@ -1,63 +1,23 @@
1
1
  module Steep
2
2
  class Project
3
3
  class Options
4
- attr_accessor :allow_fallback_any
5
- attr_accessor :allow_missing_definitions
6
- attr_accessor :allow_unknown_constant_assignment
7
- attr_accessor :allow_unknown_method_calls
8
- attr_accessor :vendor_path
9
- attr_reader :libraries
10
- attr_reader :repository_paths
11
-
12
- def initialize
13
- apply_default_typing_options!
14
- self.vendor_path = nil
15
-
16
- @libraries = []
17
- @repository_paths = []
18
- end
19
-
20
- def apply_default_typing_options!
21
- self.allow_fallback_any = true
22
- self.allow_missing_definitions = true
23
- self.allow_unknown_constant_assignment = false
24
- self.allow_unknown_method_calls = false
25
- end
26
-
27
- def apply_strict_typing_options!
28
- self.allow_fallback_any = false
29
- self.allow_missing_definitions = false
30
- self.allow_unknown_constant_assignment = false
31
- self.allow_unknown_method_calls = false
32
- end
33
-
34
- def apply_lenient_typing_options!
35
- self.allow_fallback_any = true
36
- self.allow_missing_definitions = true
37
- self.allow_unknown_constant_assignment = true
38
- self.allow_unknown_method_calls = true
39
- end
4
+ PathOptions = Struct.new(:core_root, :stdlib_root, :repo_paths, keyword_init: true) do
5
+ def customized_stdlib?
6
+ stdlib_root != nil
7
+ end
40
8
 
41
- def error_to_report?(error)
42
- case
43
- when error.is_a?(Diagnostic::Ruby::FallbackAny)
44
- !allow_fallback_any
45
- when error.is_a?(Diagnostic::Ruby::MethodDefinitionMissing)
46
- !allow_missing_definitions
47
- when error.is_a?(Diagnostic::Ruby::NoMethod)
48
- !allow_unknown_method_calls
49
- when error.is_a?(Diagnostic::Ruby::UnknownConstantAssigned)
50
- !allow_unknown_constant_assignment
51
- else
52
- true
9
+ def customized_core?
10
+ core_root != nil
53
11
  end
54
12
  end
55
13
 
56
- def merge!(hash)
57
- self.allow_fallback_any = hash[:allow_fallback_any] if hash.key?(:allow_fallback_any)
58
- self.allow_missing_definitions = hash[:allow_missing_definitions] if hash.key?(:allow_missing_definitions)
59
- self.allow_unknown_constant_assignment = hash[:allow_unknown_constant_assignment] if hash.key?(:allow_unknown_constant_assignment)
60
- self.allow_unknown_method_calls = hash[:allow_unknown_method_calls] if hash.key?(:allow_unknown_method_calls)
14
+ attr_reader :libraries
15
+ attr_accessor :paths
16
+ attr_accessor :collection_lock
17
+
18
+ def initialize
19
+ @paths = PathOptions.new(repo_paths: [])
20
+ @libraries = []
61
21
  end
62
22
  end
63
23
  end
@@ -6,12 +6,14 @@ module Steep
6
6
 
7
7
  attr_reader :source_pattern
8
8
  attr_reader :signature_pattern
9
+ attr_reader :code_diagnostics_config
9
10
 
10
- def initialize(name:, options:, source_pattern:, signature_pattern:)
11
+ def initialize(name:, options:, source_pattern:, signature_pattern:, code_diagnostics_config:)
11
12
  @name = name
12
13
  @options = options
13
14
  @source_pattern = source_pattern
14
15
  @signature_pattern = signature_pattern
16
+ @code_diagnostics_config = code_diagnostics_config
15
17
 
16
18
  @source_files = {}
17
19
  @signature_files = {}
@@ -30,20 +32,32 @@ module Steep
30
32
  end
31
33
 
32
34
  def self.construct_env_loader(options:, project:)
33
- repo = RBS::Repository.new(no_stdlib: options.vendor_path)
34
- options.repository_paths.each do |path|
35
+ repo = RBS::Repository.new(no_stdlib: options.paths.customized_stdlib?)
36
+
37
+ if options.paths.stdlib_root
38
+ repo.add(project.absolute_path(options.paths.stdlib_root))
39
+ end
40
+
41
+ options.paths.repo_paths.each do |path|
35
42
  repo.add(project.absolute_path(path))
36
43
  end
37
44
 
38
- loader = RBS::EnvironmentLoader.new(
39
- core_root: options.vendor_path ? nil : RBS::EnvironmentLoader::DEFAULT_CORE_ROOT,
40
- repository: repo
41
- )
42
- loader.add(path: options.vendor_path) if options.vendor_path
45
+ core_root_path =
46
+ if options.paths.customized_core?
47
+ if options.paths.core_root
48
+ project.absolute_path(options.paths.core_root)
49
+ end
50
+ else
51
+ RBS::EnvironmentLoader::DEFAULT_CORE_ROOT
52
+ end
53
+
54
+ loader = RBS::EnvironmentLoader.new(core_root: core_root_path, repository: repo)
55
+
43
56
  options.libraries.each do |lib|
44
57
  name, version = lib.split(/:/, 2)
45
58
  loader.add(library: name, version: version)
46
59
  end
60
+ loader.add_collection(options.collection_lock) if options.collection_lock
47
61
 
48
62
  loader
49
63
  end