steep 0.44.1 → 0.47.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) 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 +43 -0
  6. data/Gemfile +1 -4
  7. data/Gemfile.lock +73 -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/type_check_worker.rb +6 -9
  28. data/lib/steep/services/file_loader.rb +26 -19
  29. data/lib/steep/services/hover_content.rb +135 -80
  30. data/lib/steep/source.rb +12 -11
  31. data/lib/steep/type_construction.rb +435 -502
  32. data/lib/steep/type_inference/block_params.rb +3 -6
  33. data/lib/steep/type_inference/method_params.rb +483 -0
  34. data/lib/steep/type_inference/send_args.rb +599 -128
  35. data/lib/steep/typing.rb +46 -21
  36. data/lib/steep/version.rb +1 -1
  37. data/lib/steep.rb +5 -3
  38. data/sample/Steepfile +10 -3
  39. data/smoke/alias/Steepfile +2 -1
  40. data/smoke/and/Steepfile +2 -1
  41. data/smoke/array/Steepfile +2 -1
  42. data/smoke/array/test_expectations.yml +3 -3
  43. data/smoke/block/Steepfile +2 -2
  44. data/smoke/block/c.rb +0 -1
  45. data/smoke/case/Steepfile +2 -1
  46. data/smoke/class/Steepfile +2 -1
  47. data/smoke/class/test_expectations.yml +12 -15
  48. data/smoke/const/Steepfile +2 -1
  49. data/smoke/diagnostics/Steepfile +2 -1
  50. data/smoke/diagnostics/different_method_parameter_kind.rb +9 -0
  51. data/smoke/diagnostics/method_arity_mismatch.rb +2 -2
  52. data/smoke/diagnostics/method_parameter_mismatch.rb +10 -0
  53. data/smoke/diagnostics/test_expectations.yml +108 -31
  54. data/smoke/diagnostics-rbs/Steepfile +1 -1
  55. data/smoke/diagnostics-rbs/mixin-class-error.rbs +6 -0
  56. data/smoke/diagnostics-rbs/test_expectations.yml +12 -0
  57. data/smoke/diagnostics-rbs-duplicated/Steepfile +2 -1
  58. data/smoke/diagnostics-ruby-unsat/Steepfile +2 -1
  59. data/smoke/dstr/Steepfile +2 -1
  60. data/smoke/ensure/Steepfile +2 -1
  61. data/smoke/ensure/test_expectations.yml +3 -3
  62. data/smoke/enumerator/Steepfile +2 -1
  63. data/smoke/enumerator/test_expectations.yml +1 -1
  64. data/smoke/extension/Steepfile +2 -1
  65. data/smoke/extension/e.rbs +1 -1
  66. data/smoke/hash/Steepfile +2 -1
  67. data/smoke/hello/Steepfile +2 -1
  68. data/smoke/if/Steepfile +2 -1
  69. data/smoke/implements/Steepfile +2 -1
  70. data/smoke/initialize/Steepfile +2 -1
  71. data/smoke/integer/Steepfile +2 -1
  72. data/smoke/interface/Steepfile +2 -1
  73. data/smoke/kwbegin/Steepfile +2 -1
  74. data/smoke/lambda/Steepfile +2 -1
  75. data/smoke/literal/Steepfile +2 -1
  76. data/smoke/literal/test_expectations.yml +2 -2
  77. data/smoke/map/Steepfile +2 -1
  78. data/smoke/method/Steepfile +2 -1
  79. data/smoke/method/test_expectations.yml +11 -10
  80. data/smoke/module/Steepfile +2 -1
  81. data/smoke/regexp/Steepfile +2 -1
  82. data/smoke/regression/Steepfile +2 -1
  83. data/smoke/rescue/Steepfile +2 -1
  84. data/smoke/rescue/test_expectations.yml +3 -3
  85. data/smoke/self/Steepfile +2 -1
  86. data/smoke/skip/Steepfile +2 -1
  87. data/smoke/stdout/Steepfile +2 -1
  88. data/smoke/super/Steepfile +2 -1
  89. data/smoke/toplevel/Steepfile +2 -1
  90. data/smoke/toplevel/test_expectations.yml +3 -3
  91. data/smoke/tsort/Steepfile +4 -5
  92. data/smoke/tsort/test_expectations.yml +2 -2
  93. data/smoke/type_case/Steepfile +2 -1
  94. data/smoke/unexpected/Steepfile +2 -1
  95. data/smoke/yield/Steepfile +2 -1
  96. data/steep.gemspec +2 -2
  97. metadata +16 -10
  98. 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