Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor CodeWorker #229

Merged
merged 1 commit into from
Oct 2, 2020
Merged
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
3 changes: 2 additions & 1 deletion lib/steep.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@
require "steep/server/master"

require "steep/project"
require "steep/project/file"
require "steep/project/signature_file"
require "steep/project/source_file"
require "steep/project/options"
require "steep/project/target"
require "steep/project/dsl"
Expand Down
13 changes: 13 additions & 0 deletions lib/steep/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ def target_for_source_path(path)
end
end

def targets_for_path(path)
if target = target_for_source_path(path)
[target, []]
else
[
nil,
targets.select do |target|
target.possible_signature_file?(path)
end
]
end
end

def all_source_files
targets.each.with_object(Set[]) do |target, paths|
paths.merge(target.source_files.keys)
Expand Down
33 changes: 33 additions & 0 deletions lib/steep/project/signature_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Steep
class Project
class SignatureFile
attr_reader :path
attr_reader :content
attr_reader :content_updated_at

attr_reader :status

ParseErrorStatus = Struct.new(:error, :timestamp, keyword_init: true)
DeclarationsStatus = Struct.new(:declarations, :timestamp, keyword_init: true)

def initialize(path:)
@path = path
self.content = ""
end

def content=(content)
@content_updated_at = Time.now
@content = content
@status = nil
end

def load!
buffer = RBS::Buffer.new(name: path, content: content)
decls = RBS::Parser.parse_signature(buffer)
@status = DeclarationsStatus.new(declarations: decls, timestamp: Time.now)
rescue RBS::Parser::SyntaxError, RBS::Parser::SemanticsError => exn
@status = ParseErrorStatus.new(error: exn, timestamp: Time.now)
end
end
end
end
64 changes: 16 additions & 48 deletions lib/steep/project/file.rb → lib/steep/project/source_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ class SourceFile

attr_accessor :status

ParseErrorStatus = Struct.new(:error, keyword_init: true)
AnnotationSyntaxErrorStatus = Struct.new(:error, :location, keyword_init: true)
ParseErrorStatus = Struct.new(:error, :timestamp, keyword_init: true)
AnnotationSyntaxErrorStatus = Struct.new(:error, :location, :timestamp, keyword_init: true)
TypeCheckStatus = Struct.new(:typing, :source, :timestamp, keyword_init: true)
TypeCheckErrorStatus = Struct.new(:error, keyword_init: true)
TypeCheckErrorStatus = Struct.new(:error, :timestamp, keyword_init: true)

def initialize(path:)
@path = path
Expand All @@ -20,11 +20,9 @@ def initialize(path:)
end

def content=(content)
if @content != content
@content_updated_at = Time.now
@content = content
@status = nil
end
@content_updated_at = Time.now
@content = content
@status = nil
end

def errors
Expand Down Expand Up @@ -88,72 +86,42 @@ def self.type_check(source, subtyping:)

def type_check(subtyping, env_updated_at)
# skip type check
return false if status.is_a?(TypeCheckStatus) && env_updated_at <= status.timestamp
return false if status && env_updated_at <= status.timestamp

now = Time.now

parse(subtyping.factory) do |source|
typing = self.class.type_check(source, subtyping: subtyping)
@status = TypeCheckStatus.new(
typing: typing,
source: source,
timestamp: Time.now
)
@status = TypeCheckStatus.new(typing: typing, source: source, timestamp: now)
rescue RBS::NoTypeFoundError,
RBS::NoMixinFoundError,
RBS::NoSuperclassFoundError,
RBS::DuplicatedMethodDefinitionError,
RBS::InvalidTypeApplicationError => exn
# Skip logging known signature errors (they are handled with load_signatures(validate: true))
@status = TypeCheckErrorStatus.new(error: exn)
@status = TypeCheckErrorStatus.new(error: exn, timestamp: now)
rescue => exn
Steep.log_error(exn)
@status = TypeCheckErrorStatus.new(error: exn)
@status = TypeCheckErrorStatus.new(error: exn, timestamp: now)
end

true
end

def parse(factory)
now = Time.now

if status.is_a?(TypeCheckStatus)
yield status.source
else
yield self.class.parse(content, path: path, factory: factory)
end
rescue AnnotationParser::SyntaxError => exn
Steep.logger.warn { "Annotation syntax error on #{path}: #{exn.inspect}" }
@status = AnnotationSyntaxErrorStatus.new(error: exn, location: exn.location)
@status = AnnotationSyntaxErrorStatus.new(error: exn, location: exn.location, timestamp: now)
rescue ::Parser::SyntaxError, EncodingError => exn
Steep.logger.warn { "Source parsing error on #{path}: #{exn.inspect}" }
@status = ParseErrorStatus.new(error: exn)
end
end

class SignatureFile
attr_reader :path
attr_reader :content
attr_reader :content_updated_at

attr_reader :status

ParseErrorStatus = Struct.new(:error, keyword_init: true)
DeclarationsStatus = Struct.new(:declarations, keyword_init: true)

def initialize(path:)
@path = path
self.content = ""
end

def content=(content)
@content_updated_at = Time.now
@content = content
@status = nil
end

def load!
buffer = RBS::Buffer.new(name: path, content: content)
decls = RBS::Parser.parse_signature(buffer)
@status = DeclarationsStatus.new(declarations: decls)
rescue RBS::Parser::SyntaxError, RBS::Parser::SemanticsError => exn
@status = ParseErrorStatus.new(error: exn)
@status = ParseErrorStatus.new(error: exn, timestamp: now)
end
end
end
Expand Down
9 changes: 5 additions & 4 deletions lib/steep/project/target.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def load_signatures(validate:)
when TypeCheckStatus
status.timestamp
end
now = Time.now

updated_files = []

Expand Down Expand Up @@ -169,11 +170,11 @@ def load_signatures(validate:)
validator.validate()

if validator.no_error?
yield env, check, Time.now
yield env, check, now
else
@status = SignatureValidationErrorStatus.new(
errors: validator.each_error.to_a,
timestamp: Time.now
timestamp: now
)
end
else
Expand All @@ -187,11 +188,11 @@ def load_signatures(validate:)
location: exn.decls[0].location
)
],
timestamp: Time.now
timestamp: now
)
rescue => exn
Steep.log_error exn
@status = SignatureOtherErrorStatus.new(error: exn, timestamp: Time.now)
@status = SignatureOtherErrorStatus.new(error: exn, timestamp: now)
end
end

Expand Down
76 changes: 31 additions & 45 deletions lib/steep/server/code_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,32 @@ class CodeWorker < BaseWorker

include Utils

attr_reader :target_files
attr_reader :typecheck_paths
attr_reader :queue

def initialize(project:, reader:, writer:, queue: Queue.new)
super(project: project, reader: reader, writer: writer)

@target_files = {}
@typecheck_paths = Set[]
@queue = queue
end

def enqueue_type_check(target:, path:, version: target_files[path])
Steep.logger.info "Enqueueing type check: #{path}(#{version})@#{target.name}..."
target_files[path] = version
queue << [path, version, target]
end

def each_type_check_subject(path:, version:)
case
when !(updated_targets = project.targets.select {|target| target.signature_file?(path) }).empty?
updated_targets.each do |target|
target_files.each_key do |path|
if target.source_file?(path)
yield target, path, target_files[path]
end
end
end

when target = project.target_for_source_path(path)
if target_files.key?(path)
yield target, path, version
end
end
def enqueue_type_check(target:, path:)
Steep.logger.info "Enqueueing type check: #{target.name}::#{path}..."
queue << [target, path]
end

def typecheck_file(path, target)
Steep.logger.info "Starting type checking: #{path}@#{target.name}..."
Steep.logger.info "Starting type checking: #{target.name}::#{path}..."

source = target.source_files[path]
target.type_check(target_sources: [source], validate_signatures: false)

Steep.logger.info "Finished type checking: #{path}@#{target.name}"
if target.status.is_a?(Project::Target::TypeCheckStatus) && target.status.type_check_sources.empty?
Steep.logger.debug "Skipped type checking: #{target.name}::#{path}"
else
Steep.logger.info "Finished type checking: #{target.name}::#{path}"
end

diagnostics = source_diagnostics(source, target.options)

Expand Down Expand Up @@ -109,42 +94,43 @@ def handle_request(request)
# Don't respond to initialize request, but start type checking.
project.targets.each do |target|
target.source_files.each_key do |path|
if target_files.key?(path)
enqueue_type_check(target: target, path: path, version: target_files[path])
if typecheck_paths.include?(path)
enqueue_type_check(target: target, path: path)
end
end
end

when "workspace/executeCommand"
if request[:params][:command] == "steep/registerSourceToWorker"
paths = request[:params][:arguments].map {|arg| source_path(URI.parse(arg)) }
paths.each do |path|
target_files[path] = 0
end
typecheck_paths.merge(paths)
end

when "textDocument/didChange"
update_source(request) do |path, version|
if target_files.key?(path)
target_files[path] = version
update_source(request) do |path, _|
source_target, signature_targets = project.targets_for_path(path)

if source_target
if typecheck_paths.include?(path)
enqueue_type_check(target: source_target, path: path)
end
end
end

path = source_path(URI.parse(request[:params][:textDocument][:uri]))
version = request[:params][:textDocument][:version]
each_type_check_subject(path: path, version: version) do |target, path, version|
enqueue_type_check(target: target, path: path, version: version)
signature_targets.each do |target|
target.source_files.each_key do |source_path|
if typecheck_paths.include?(source_path)
enqueue_type_check(target: target, path: source_path)
end
end
end
end
end
end

def handle_job(job)
path, version, target = job
if !version || target_files[path] == version
typecheck_file(path, target)
else
Steep.logger.info "Skipping type check: #{path}@#{target.name}, queued version=#{version}, latest version=#{target_files[path]}"
end
target, path = job

typecheck_file(path, target)
end
end
end
Expand Down
36 changes: 19 additions & 17 deletions lib/steep/server/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,30 @@ def update_source(request)

changes = request[:params][:contentChanges]

if target = project.target_for_source_path(path)
source_target, signature_targets = project.targets_for_path(path)

if source_target
changes.each do |change|
case
when target.source_file?(path)
Steep.logger.debug { "Updating source in #{target.name}: path=#{path}" }
target.update_source(path) {|text| apply_change(change, text) }
when target.possible_source_file?(path)
Steep.logger.debug { "Adding source to #{target.name}: path=#{path}" }
target.add_source(path, change[:text])
when source_target.source_file?(path)
Steep.logger.debug { "Updating source in #{source_target.name}: path=#{path}" }
source_target.update_source(path) {|text| apply_change(change, text) }
when source_target.possible_source_file?(path)
Steep.logger.debug { "Adding source to #{source_target.name}: path=#{path}" }
source_target.add_source(path, change[:text])
end
end
else
end

signature_targets.each do |target|
changes.each do |change|
project.targets.each do |target|
case
when target.signature_file?(path)
Steep.logger.debug { "Updating signature in #{target.name}: path=#{path}" }
target.update_signature(path) {|text| apply_change(change, text) }
when target.possible_signature_file?(path)
Steep.logger.debug { "Adding signature to #{target.name}: path=#{path}" }
target.add_signature(path, change[:text])
end
case
when target.signature_file?(path)
Steep.logger.debug { "Updating signature in #{target.name}: path=#{path}" }
target.update_signature(path) {|text| apply_change(change, text) }
when target.possible_signature_file?(path)
Steep.logger.debug { "Adding signature to #{target.name}: path=#{path}" }
target.add_signature(path, change[:text])
end
end
end
Expand Down
Loading