Class: Steep::Services::SignatureService

Inherits:
Object
  • Object
show all
Defined in:
lib/steep/services/signature_service.rb

Defined Under Namespace

Classes: AncestorErrorStatus, LoadedStatus, SyntaxErrorStatus

Constant Summary collapse

RBSFileStatus =
_ = Struct.new(:path, :content, :source, keyword_init: true)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(status:, implicitly_returns_nil:) ⇒ SignatureService

Returns a new instance of SignatureService.



83
84
85
86
# File 'lib/steep/services/signature_service.rb', line 83

def initialize(status:, implicitly_returns_nil:)
  @status = status
  @implicitly_returns_nil = implicitly_returns_nil
end

Instance Attribute Details

#implicitly_returns_nilObject (readonly)

Returns the value of attribute implicitly_returns_nil.



81
82
83
# File 'lib/steep/services/signature_service.rb', line 81

def implicitly_returns_nil
  @implicitly_returns_nil
end

#statusObject (readonly)

Returns the value of attribute status.



4
5
6
# File 'lib/steep/services/signature_service.rb', line 4

def status
  @status
end

Class Method Details

.load_from(loader, implicitly_returns_nil:) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/steep/services/signature_service.rb', line 88

def self.load_from(loader, implicitly_returns_nil:)
  env = RBS::Environment.from_loader(loader).resolve_type_names
  builder = RBS::DefinitionBuilder.new(env: env)
  status = LoadedStatus.new(builder: builder, files: {}, implicitly_returns_nil: implicitly_returns_nil)
  new(status: status, implicitly_returns_nil: implicitly_returns_nil)
rescue RBS::ParsingError => exn
  # When library RBS contains syntax error, load only *core* libraries and set `SyntaxErrorStatus`.
  core_loader = RBS::EnvironmentLoader.new(core_root: loader.core_root)
  core_env = RBS::Environment.from_loader(core_loader).resolve_type_names
  status = SyntaxErrorStatus.new(
    files: {},
    changed_paths: Set[],
    diagnostics: [Diagnostic::Signature.from_rbs_error(exn, factory: _ = nil)],
    last_builder: RBS::DefinitionBuilder.new(env: core_env)
  )
  service = new(status: status, implicitly_returns_nil: implicitly_returns_nil)
  # Add the failed library path to env_rbs_paths so it's recognized as a library path by TypeCheckService
  if exn.location
    service.env_rbs_paths << Pathname(exn.location.buffer.name)
  end
  service
end

Instance Method Details

#add_descendants(graph:, names:, set:) ⇒ Object



459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# File 'lib/steep/services/signature_service.rb', line 459

def add_descendants(graph:, names:, set:)
  set.merge(names)
  names.each do |name|
    case
    when name.interface?
      graph.each_descendant(RBS::AncestorGraph::InstanceNode.new(type_name: name)) do |node|
        set << node.type_name
      end
    when name.class?
      graph.each_descendant(RBS::AncestorGraph::InstanceNode.new(type_name: name)) do |node|
        set << node.type_name
      end
      graph.each_descendant(RBS::AncestorGraph::SingletonNode.new(type_name: name)) do |node|
        set << node.type_name
      end
    end
  end
end

#add_nested_decls(env:, names:, set:) ⇒ Object



478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
# File 'lib/steep/services/signature_service.rb', line 478

def add_nested_decls(env:, names:, set:)
  tops = names.each_with_object(Set[]) do |name, tops|
    unless name.namespace.empty?
      tops << name.namespace.path[0]
    end
  end

  env.class_decls.each_key do |name|
    unless name.namespace.empty?
      if tops.include?(name.namespace.path[0])
        set << name
      end
    end
  end

  env.interface_decls.each_key do |name|
    unless name.namespace.empty?
      if tops.include?(name.namespace.path[0])
        set << name
      end
    end
  end
end

#apply_changes(files, changes) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/steep/services/signature_service.rb', line 171

def apply_changes(files, changes)
  Steep.logger.tagged "#apply_changes" do
    Steep.measure2 "Applying change" do |sampler|
      changes.each.with_object({}) do |(path, cs), update|  # $ Hash[Pathname, file_status]
        sampler.sample "#{path}" do
          old_file = files.fetch(path, nil)

          case old_file
          when RBSFileStatus
            old_text = old_file.content
            new_file = load_rbs_file(path, old_text, cs)
          when RBS::Source::Ruby
            old_text = old_file.buffer.content
            new_file = load_ruby_file(path, old_text, cs)
          when nil
            # New file: Detect based on the file name
            if path.extname == ".rbs"
              # RBS File
              new_file = load_rbs_file(path, "", cs)
            else
              # Ruby File
              new_file = load_ruby_file(path, "", cs)
            end
          end

          update[path] = new_file
        end
      end
    end
  end
end

#const_decls(paths:, env:) ⇒ Object



426
427
428
429
430
431
432
# File 'lib/steep/services/signature_service.rb', line 426

def const_decls(paths:, env:)
  env.constant_decls.filter do |_, entry|
    if location = entry.decl.location
      paths.include?(Pathname(location.buffer.name))
    end
  end
end

#current_subtypingObject



165
166
167
168
169
# File 'lib/steep/services/signature_service.rb', line 165

def current_subtyping
  if status.is_a?(LoadedStatus)
    status.subtyping
  end
end

#each_rbs_path(&block) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/steep/services/signature_service.rb', line 117

def each_rbs_path(&block)
  if block
    env_rbs_paths.each do |path|
      unless files.key?(path)
        yield path
      end
    end

    files.each_key(&block)
  else
    enum_for :each_rbs_path
  end
end

#env_rbs_pathsObject



111
112
113
114
115
# File 'lib/steep/services/signature_service.rb', line 111

def env_rbs_paths
  @env_rbs_paths ||= latest_env.buffers.each.with_object(Set[]) do |buffer, set|
    set << Pathname(buffer.name)
  end
end

#error_file?(file) ⇒ Boolean

Returns:

  • (Boolean)


236
237
238
239
240
241
242
243
# File 'lib/steep/services/signature_service.rb', line 236

def error_file?(file)
  case file
  when RBSFileStatus
    !file.source.is_a?(RBS::Source::RBS)
  when RBS::Source::Ruby
    false
  end
end

#filesObject



131
132
133
# File 'lib/steep/services/signature_service.rb', line 131

def files
  status.files
end

#global_decls(paths:, env: latest_env) ⇒ Object



434
435
436
437
438
439
440
# File 'lib/steep/services/signature_service.rb', line 434

def global_decls(paths:, env: latest_env)
  env.global_decls.filter do |_, entry|
    if location = entry.decl.location
      paths.include?(Pathname(location.buffer.name))
    end
  end
end

#latest_builderObject



148
149
150
151
152
153
154
155
# File 'lib/steep/services/signature_service.rb', line 148

def latest_builder
  case status = status()
  when LoadedStatus
    status.builder
  when SyntaxErrorStatus, AncestorErrorStatus
    status.last_builder
  end
end

#latest_constant_resolverObject



161
162
163
# File 'lib/steep/services/signature_service.rb', line 161

def latest_constant_resolver
  status.constant_resolver
end

#latest_envObject



144
145
146
# File 'lib/steep/services/signature_service.rb', line 144

def latest_env
  latest_builder.env
end

#latest_rbs_indexObject



157
158
159
# File 'lib/steep/services/signature_service.rb', line 157

def latest_rbs_index
  status.rbs_index
end

#load_rbs_file(path, old_text, changes) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/steep/services/signature_service.rb', line 203

def load_rbs_file(path, old_text, changes)
  content = changes.reduce(old_text) do |text, change| # $ String
    change.apply_to(text)
  end

  buffer = RBS::Buffer.new(name: path, content: content)
  source =
    begin
      _, dirs, decls = RBS::Parser.parse_signature(buffer)
      RBS::Source::RBS.new(buffer, dirs, decls)
    rescue ArgumentError => exn
      Diagnostic::Signature::UnexpectedError.new(
        message: exn.message,
        location: RBS::Location.new(buffer: buffer, start_pos: 0, end_pos: content.size)
      )
    rescue RBS::ParsingError => exn
      exn
    end

  RBSFileStatus.new(path: path, content: content, source: source)
end

#load_ruby_file(path, old_text, changes) ⇒ Object



225
226
227
228
229
230
231
232
233
234
# File 'lib/steep/services/signature_service.rb', line 225

def load_ruby_file(path, old_text, changes)
  content = changes.reduce(old_text) do |text, change| # $ String
    change.apply_to(text)
  end

  buffer = RBS::Buffer.new(name: path, content: content)
  prism = Prism.parse(buffer.content, filepath: path.to_s)
  result = RBS::InlineParser.parse(buffer, prism)
  RBS::Source::Ruby.new(buffer, prism, result.declarations, result.diagnostics)
end

#pending_changed_pathsObject



135
136
137
138
139
140
141
142
# File 'lib/steep/services/signature_service.rb', line 135

def pending_changed_paths
  case status = status()
  when LoadedStatus
    Set[]
  when SyntaxErrorStatus, AncestorErrorStatus
    Set.new(status.changed_paths)
  end
end

#rescue_rbs_error(errors) ⇒ Object



369
370
371
372
373
374
375
# File 'lib/steep/services/signature_service.rb', line 369

def rescue_rbs_error(errors)
  begin
    yield
  rescue RBS::BaseError => exn
    errors << exn
  end
end

#type_name_from_decl(decl, set:) ⇒ Object



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/steep/services/signature_service.rb', line 442

def type_name_from_decl(decl, set:)
  case decl
  when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module, RBS::AST::Declarations::Interface
    set << decl.name

    decl.members.each do |member|
      if member.is_a?(RBS::AST::Declarations::Base)
        type_name_from_decl(member, set: set)
      end
    end
  when RBS::AST::Declarations::TypeAlias
    set << decl.name
  when RBS::AST::Declarations::ClassAlias, RBS::AST::Declarations::ModuleAlias
    set << decl.new_name
  end
end

#type_names(paths:, env:) ⇒ Object



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/steep/services/signature_service.rb', line 404

def type_names(paths:, env:)

  Hash.new {}
  set = Set[] #: Set[RBS::TypeName]

  env.each_rbs_source do |source|
    next unless paths.include?(source.buffer.name)
    source.each_type_name do |type_name|
      set << type_name
    end
  end

  env.each_ruby_source do |source|
    next unless paths.include?(source.buffer.name)
    source.each_type_name do |type_name|
      set << type_name
    end
  end

  set
end

#update(changes) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/steep/services/signature_service.rb', line 245

def update(changes)
  Steep.logger.tagged "#update" do
    updates = apply_changes(files, changes)
    paths = Set.new(updates.each_key)
    paths.merge(pending_changed_paths)

    if updates.each_value.any? {|file| error_file?(file) }
      diagnostics = [] #: Array[Diagnostic::Signature::Base]

      updates.each_value do |file|
        if error_file?(file)
          if file.is_a?(RBSFileStatus)
            diagnostic =
              case file.source
              when Diagnostic::Signature::Base
                file.source
              when RBS::ParsingError
                Diagnostic::Signature.from_rbs_error(file.source, factory: _ = nil)
              else
                raise "file (#{file.path}) must be an error"
              end

            diagnostics << diagnostic
          end
        end
      end

      @status = SyntaxErrorStatus.new(
        files: self.files.merge(updates),
        diagnostics: diagnostics,
        last_builder: latest_builder,
        changed_paths: paths
      )
    else
      files = self.files.merge(updates)
      updated_files = files.slice(*paths.to_a)
      result =
        Steep.measure "#update_env with updated #{paths.size} files" do
          update_env(updated_files, paths: paths)
        end

      @status = case result
                when Array
                  AncestorErrorStatus.new(
                    changed_paths: paths,
                    last_builder: latest_builder,
                    diagnostics: result,
                    files: files
                  )
                when RBS::DefinitionBuilder::AncestorBuilder
                  builder2 = update_builder(ancestor_builder: result, paths: paths)
                  LoadedStatus.new(builder: builder2, files: files, implicitly_returns_nil: implicitly_returns_nil)
                end
    end
  end
end

#update_builder(ancestor_builder:, paths:) ⇒ Object



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/steep/services/signature_service.rb', line 377

def update_builder(ancestor_builder:, paths:)
  Steep.measure "#update_builder with #{paths.size} files" do
    changed_names = Set[]

    old_definition_builder = latest_builder
    old_env = old_definition_builder.env
    old_names = type_names(paths: paths, env: old_env)
    old_ancestor_builder = old_definition_builder.ancestor_builder
    old_graph = RBS::AncestorGraph.new(env: old_env, ancestor_builder: old_ancestor_builder)
    add_descendants(graph: old_graph, names: old_names, set: changed_names)
    add_nested_decls(env: old_env, names: old_names, set: changed_names)

    new_env = ancestor_builder.env
    new_ancestor_builder = ancestor_builder
    new_names = type_names(paths: paths, env: new_env)
    new_graph = RBS::AncestorGraph.new(env: new_env, ancestor_builder: new_ancestor_builder)
    add_descendants(graph: new_graph, names: new_names, set: changed_names)
    add_nested_decls(env: new_env, names: new_names, set: changed_names)

    old_definition_builder.update(
      env: new_env,
      ancestor_builder: new_ancestor_builder,
      except: changed_names
    )
  end
end

#update_env(updated_files, paths:) ⇒ Object



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/steep/services/signature_service.rb', line 302

def update_env(updated_files, paths:)
  Steep.logger.tagged "#update_env" do
    errors = [] #: Array[RBS::BaseError]
    new_decls = Set[].compare_by_identity #: Set[RBS::AST::Declarations::t | RBS::AST::Ruby::Declarations::t]

    env =
      Steep.measure "Deleting out of date decls" do
        latest_env.unload(paths)
      end

    Steep.measure "Loading new decls" do
      updated_files.each_value do |content|
        case content
        when RBSFileStatus
          (source = content.source).is_a?(RBS::Source::RBS) or raise "Cannot be an error"
          env.add_source(source)
          new_decls.merge(source.declarations)
        when RBS::Source::Ruby
          env.add_source(content)
          new_decls.merge(content.declarations)
        end
      rescue RBS::LoadingError => exn
        errors << exn
      end
    end

    Steep.measure "validate type params" do
      begin
        env.validate_type_params
      rescue RBS::LoadingError => exn
        errors << exn
      end
    end

    unless errors.empty?
      return errors.map {|error|
        # Factory will not be used because of the possible error types.
        Diagnostic::Signature.from_rbs_error(error, factory: _ = nil)
      }
    end

    Steep.measure "resolve type names with #{new_decls.size} top-level decls" do
      env = env.resolve_type_names(only: new_decls)
    end

    builder = RBS::DefinitionBuilder::AncestorBuilder.new(env: env)

    Steep.measure("Pre-loading one ancestors") do
      builder.env.class_decls.each_key do |type_name|
        rescue_rbs_error(errors) { builder.one_instance_ancestors(type_name) }
        rescue_rbs_error(errors) { builder.one_singleton_ancestors(type_name) }
      end
      builder.env.interface_decls.each_key do |type_name|
        rescue_rbs_error(errors) { builder.one_interface_ancestors(type_name) }
      end
    end

    unless errors.empty?
      errors.uniq! { |e| [e.class, e.message] }
      factory = AST::Types::Factory.new(builder: RBS::DefinitionBuilder.new(env: env, ancestor_builder: builder))
      return errors.map {|error| Diagnostic::Signature.from_rbs_error(error, factory: factory) }
    end

    builder
  end
end