Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 45 additions & 28 deletions lib/steep/server/type_check_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -237,55 +237,58 @@ def handle_job(job)
if job.guid == current_type_check_guid
Steep.logger.info { "Processing ValidateAppSignature for guid=#{job.guid}, path=#{job.path}" }

formatter = Diagnostic::LSPFormatter.new({}, **{})
reporting_typecheck_progress(job) do
formatter = Diagnostic::LSPFormatter.new({}, **{})

diagnostics = service.validate_signature(path: project.relative_path(job.path), target: job.target)
diagnostics = service.validate_signature(path: project.relative_path(job.path), target: job.target)

typecheck_progress(
path: job.path,
guid: job.guid,
target: job.target,
diagnostics: diagnostics.filter_map { formatter.format(_1) }
)
diagnostics.filter_map { formatter.format(_1) }
end
end

when ValidateLibrarySignatureJob
if job.guid == current_type_check_guid
Steep.logger.info { "Processing ValidateLibrarySignature for guid=#{job.guid}, path=#{job.path}" }

formatter = Diagnostic::LSPFormatter.new({}, **{})
diagnostics = service.validate_signature(path: job.path, target: job.target)
reporting_typecheck_progress(job) do
formatter = Diagnostic::LSPFormatter.new({}, **{})
diagnostics = service.validate_signature(path: job.path, target: job.target)

typecheck_progress(path: job.path, guid: job.guid, target: job.target, diagnostics: diagnostics.filter_map { formatter.format(_1) })
diagnostics.filter_map { formatter.format(_1) }
end
end

when TypeCheckCodeJob
if job.guid == current_type_check_guid
Steep.logger.info { "Processing TypeCheckCodeJob for guid=#{job.guid}, path=#{job.path}, target=#{job.target.name}" }
group_target = project.group_for_source_path(job.path) || job.target
formatter = Diagnostic::LSPFormatter.new(group_target.code_diagnostics_config)
relative_path = project.relative_path(job.path)
diagnostics = service.typecheck_source(path: relative_path, target: job.target)
typecheck_progress(path: job.path, guid: job.guid, target: job.target, diagnostics: diagnostics&.filter_map { formatter.format(_1) })
reporting_typecheck_progress(job) do
group_target = project.group_for_source_path(job.path) || job.target
formatter = Diagnostic::LSPFormatter.new(group_target.code_diagnostics_config)
relative_path = project.relative_path(job.path)
diagnostics = service.typecheck_source(path: relative_path, target: job.target)
diagnostics&.filter_map { formatter.format(_1) }
end
end

when TypeCheckInlineCodeJob
if job.guid == current_type_check_guid
Steep.logger.info { "Processing TypeCheckInlineCodeJob for guid=#{job.guid}, path=#{job.path}, target=#{job.target.name}" }
group_target = project.group_for_inline_source_path(job.path) || job.target
formatter = Diagnostic::LSPFormatter.new(group_target.code_diagnostics_config)
relative_path = project.relative_path(job.path)
diagnostics = service.typecheck_source(path: relative_path, target: job.target) #: Array[Diagnostic::Ruby::Base | Diagnostic::Signature::Base] | nil
signature_diagnostics = service.validate_signature(path: relative_path, target: job.target)
if diagnostics
diagnostics.concat(signature_diagnostics)
else
unless signature_diagnostics.empty?
diagnostics = signature_diagnostics
reporting_typecheck_progress(job) do
group_target = project.group_for_inline_source_path(job.path) || job.target
formatter = Diagnostic::LSPFormatter.new(group_target.code_diagnostics_config)
relative_path = project.relative_path(job.path)
diagnostics = service.typecheck_source(path: relative_path, target: job.target) #: Array[Diagnostic::Ruby::Base | Diagnostic::Signature::Base] | nil
signature_diagnostics = service.validate_signature(path: relative_path, target: job.target)
if diagnostics
diagnostics.concat(signature_diagnostics)
else
unless signature_diagnostics.empty?
diagnostics = signature_diagnostics
end
end
end

typecheck_progress(path: job.path, guid: job.guid, target: job.target, diagnostics: diagnostics&.filter_map { formatter.format(_1) })
diagnostics&.filter_map { formatter.format(_1) }
end
end

when WorkspaceSymbolJob
Expand Down Expand Up @@ -314,6 +317,20 @@ def typecheck_progress(guid:, path:, target:, diagnostics:)
writer.write(CustomMethods::TypeCheck__Progress.notification({ guid: guid, path: path.to_s, target: target.name.to_s, diagnostics: diagnostics }))
end

# Reports the file to the master even when the given block raises an error
#
# The master waits for `$/steep/typeCheck/progress` of every file it assigned, and the type check session
# never finishes if a worker skips reporting a file, for example because building a definition
# from the RBS environment raised an error during type checking.
#
def reporting_typecheck_progress(job)
diagnostics = yield
typecheck_progress(path: job.path, guid: job.guid, target: job.target, diagnostics: diagnostics)
rescue StandardError
typecheck_progress(path: job.path, guid: job.guid, target: job.target, diagnostics: nil)
raise
end

def workspace_symbol_result(query)
Steep.measure "Generating workspace symbol list for query=`#{query}`" do
provider = Index::SignatureSymbolProvider.new(project: project, assignment: assignment)
Expand Down
8 changes: 8 additions & 0 deletions sig/steep/server/type_check_worker.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ module Steep

def typecheck_progress: (guid: String, path: Pathname, target: Project::Target, diagnostics: Array[LSPDiagnostic::json]?) -> void

# Reports the file to the master even when the given block raises an error
#
# The master waits for `$/steep/typeCheck/progress` of every file it assigned, and the type check session
# never finishes if a worker skips reporting a file, for example because building a definition
# from the RBS environment raised an error during type checking.
#
def reporting_typecheck_progress: (ValidateAppSignatureJob | ValidateLibrarySignatureJob | TypeCheckCodeJob | TypeCheckInlineCodeJob job) { () -> Array[LSPDiagnostic::json]? } -> void

def workspace_symbol_result: (untyped query) -> untyped

def stats_result: () -> Array[StatsCalculator::stats]
Expand Down
2 changes: 2 additions & 0 deletions sig/test/type_check_worker_test.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class TypeCheckWorkerTest < Minitest::Test

def test_handle_job_typecheck_code_diagnostics: () -> untyped

def test_handle_job_typecheck_code_error: () -> untyped

def test_handle_job_typecheck_skip: () -> untyped

def test_handle_job_typecheck_inline__no_error: () -> untyped
Expand Down
56 changes: 56 additions & 0 deletions test/type_check_worker_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,62 @@ def world: () -> void
end
end

def test_handle_job_typecheck_code_error
in_tmpdir do
with_master_read_queue do |master_read_queue|
project = Project.new(steepfile_path: current_dir + "Steepfile")
Project::DSL.parse(project, <<~RUBY)
target :lib do
check "lib"
signature "sig"
end
RUBY

worker = Server::TypeCheckWorker.new(
project: project,
assignment: assignment,
commandline_args: [],
reader: worker_reader,
writer: worker_writer
)

worker.instance_variable_set(:@current_type_check_guid, "guid")

{}.tap do |changes|
changes[Pathname("lib/hello.rb")] = [Services::ContentChange.string(<<~RUBY)]
Hello.new.world()
RUBY
# `Module#ruby2_keywords` conflicts with the *private* method in core, and
# building a definition raises an error during type checking, not validation.
changes[Pathname("sig/hello.rbs")] = [Services::ContentChange.string(<<~RBS)]
class Hello
def world: () -> void
end

class Module
def ruby2_keywords: (*Symbol) -> void
end
RBS
worker.handle_job(TypeCheckWorker::StartTypeCheckJob.new(guid: "guid", changes: changes))
end

job = TypeCheckWorker::TypeCheckCodeJob.new(guid: "guid", path: current_dir + "lib/hello.rb", target: project.targets[0])
assert_raises(RBS::DuplicatedMethodDefinitionError) do
worker.handle_job(job)
end

# The file is reported to the master even when the type check fails, so that
# the master doesn't wait for the file forever
master_read_queue.pop.tap do |message|
assert_equal TypeCheck__Progress::METHOD, message[:method]
assert_equal "guid", message[:params][:guid]
assert_equal (current_dir + "lib/hello.rb").to_s, message[:params][:path]
assert_nil message[:params][:diagnostics]
end
end
end
end

def test_handle_job_typecheck_skip
in_tmpdir do
with_master_read_queue do |master_read_queue|
Expand Down
Loading