Skip to content

Commit

Permalink
Merge pull request #232 from soutaro/interaction-filter
Browse files Browse the repository at this point in the history
Run type checker for interactions without unrelated defs
  • Loading branch information
soutaro authored Oct 4, 2020
2 parents c88deaa + c549380 commit d118b5e
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 123 deletions.
12 changes: 7 additions & 5 deletions lib/steep/project/completion_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ def initialize(source_text:, path:, subtyping:)
@subtyping = subtyping
end

def type_check!(text)
def type_check!(text, line:, column:)
@modified_text = text

Steep.measure "parsing" do
@source = SourceFile.parse(text, path: path, factory: subtyping.factory)
@source = SourceFile
.parse(text, path: path, factory: subtyping.factory)
.without_unrelated_defs(line: line, column: column)
end

Steep.measure "typechecking" do
Expand All @@ -53,7 +55,7 @@ def run(line:, column:)
begin
Steep.logger.tagged "completion_provider#run(line: #{line}, column: #{column})" do
Steep.measure "type_check!" do
type_check!(source_text)
type_check!(source_text, line: line, column: column)
end
end

Expand All @@ -66,11 +68,11 @@ def run(line:, column:)
case possible_trigger
when "."
source_text[index-1] = " "
type_check!(source_text)
type_check!(source_text, line: line, column: column)
items_for_dot(position: position)
when "@"
source_text[index-1] = " "
type_check!(source_text)
type_check!(source_text, line: line, column: column)
items_for_atmark(position: position)
else
[]
Expand Down
171 changes: 91 additions & 80 deletions lib/steep/project/hover_content.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,102 +36,113 @@ def method_definition_for(factory, type_name, singleton_method: nil, instance_me
end
end

def typecheck(target, path:, line:, column:)
target.type_check(target_sources: [], validate_signatures: false)

case (status = target.status)
when Project::Target::TypeCheckStatus
subtyping = status.subtyping
source = SourceFile
.parse(target.source_files[path].content, path: path, factory: subtyping.factory)
.without_unrelated_defs(line: line, column: column)
SourceFile.type_check(source, subtyping: subtyping)
end
rescue
nil
end

def content_for(path:, line:, column:)
target = project.targets.find {|target| target.source_file?(path) }
target = project.target_for_source_path(path)

if target
source_file = target.source_files[path]
target.type_check(target_sources: [source_file], validate_signatures: false)
typing = typecheck(target, path: path, line: line, column: column) or return

node, *parents = typing.source.find_nodes(line: line, column: column)

if node
case node.type
when :lvar
var_name = node.children[0]
context = typing.context_at(line: line, column: column)
var_type = context.lvar_env[var_name.name] || AST::Types::Any.new(location: nil)

case (status = source_file.status)
when SourceFile::TypeCheckStatus
node, *parents = status.source.find_nodes(line: line, column: column)
VariableContent.new(node: node, name: var_name.name, type: var_type, location: node.location.name)
when :lvasgn
var_name, rhs = node.children
context = typing.context_at(line: line, column: column)
type = context.lvar_env[var_name.name] || typing.type_of(node: rhs)

if node
case node.type
when :lvar
var_name = node.children[0]
context = status.typing.context_at(line: line, column: column)
var_type = context.lvar_env[var_name.name] || AST::Types::Any.new(location: nil)
VariableContent.new(node: node, name: var_name.name, type: type, location: node.location.name)
when :send
receiver, method_name, *_ = node.children

VariableContent.new(node: node, name: var_name.name, type: var_type, location: node.location.name)
when :lvasgn
var_name, rhs = node.children
context = status.typing.context_at(line: line, column: column)
type = context.lvar_env[var_name.name] || status.typing.type_of(node: rhs)

VariableContent.new(node: node, name: var_name.name, type: type, location: node.location.name)
when :send
receiver, method_name, *_ = node.children
result_node = if parents[0]&.type == :block
parents[0]
else
node
end

context = typing.context_at(line: line, column: column)

result_node = if parents[0]&.type == :block
parents[0]
receiver_type = if receiver
typing.type_of(node: receiver)
else
node
context.self_type
end

context = status.typing.context_at(line: line, column: column)

receiver_type = if receiver
status.typing.type_of(node: receiver)
else
context.self_type
end

factory = context.type_env.subtyping.factory
method_name, definition = case receiver_type
when AST::Types::Name::Instance
method_definition = method_definition_for(factory, receiver_type.name, instance_method: method_name)
if method_definition&.defined_in
owner_name = method_definition.defined_in
[
InstanceMethodName.new(owner_name, method_name),
method_definition
]
end
when AST::Types::Name::Singleton
method_definition = method_definition_for(factory, receiver_type.name, singleton_method: method_name)
if method_definition&.defined_in
owner_name = method_definition.defined_in
[
SingletonMethodName.new(owner_name, method_name),
method_definition
]
end
else
nil
factory = context.type_env.subtyping.factory
method_name, definition = case receiver_type
when AST::Types::Name::Instance
method_definition = method_definition_for(factory, receiver_type.name, instance_method: method_name)
if method_definition&.defined_in
owner_name = method_definition.defined_in
[
InstanceMethodName.new(owner_name, method_name),
method_definition
]
end

MethodCallContent.new(
node: node,
method_name: method_name,
type: status.typing.type_of(node: result_node),
definition: definition,
location: result_node.location.expression
)
when :def, :defs
context = status.typing.context_at(line: line, column: column)
method_context = context.method_context

if method_context && method_context.method
DefinitionContent.new(
node: node,
method_name: method_context.name,
method_type: method_context.method_type,
definition: method_context.method,
location: node.loc.expression
)
end
else
type = status.typing.type_of(node: node)

TypeContent.new(
when AST::Types::Name::Singleton
method_definition = method_definition_for(factory, receiver_type.name, singleton_method: method_name)
if method_definition&.defined_in
owner_name = method_definition.defined_in
[
SingletonMethodName.new(owner_name, method_name),
method_definition
]
end
else
nil
end

MethodCallContent.new(
node: node,
method_name: method_name,
type: typing.type_of(node: result_node),
definition: definition,
location: result_node.location.expression
)
when :def, :defs
context = typing.context_at(line: line, column: column)
method_context = context.method_context

if method_context && method_context.method
DefinitionContent.new(
node: node,
type: type,
location: node.location.expression
method_name: method_context.name,
method_type: method_context.method_type,
definition: method_context.method,
location: node.loc.expression
)
end
else
type = typing.type_of(node: node)

TypeContent.new(
node: node,
type: type,
location: node.location.expression
)
end
end
end
Expand Down
80 changes: 42 additions & 38 deletions lib/steep/server/interaction_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,23 @@ def handle_request(request)

def response_to_hover(path:, line:, column:)
Steep.logger.tagged "#response_to_hover" do
Steep.logger.debug { "path=#{path}, line=#{line}, column=#{column}" }

hover = Project::HoverContent.new(project: project)
content = hover.content_for(path: path, line: line+1, column: column+1)
if content
range = content.location.yield_self do |location|
start_position = { line: location.line - 1, character: location.column }
end_position = { line: location.last_line - 1, character: location.last_column }
{ start: start_position, end: end_position }
end
Steep.measure "Generating response" do
Steep.logger.info { "path=#{path}, line=#{line}, column=#{column}" }

hover = Project::HoverContent.new(project: project)
content = hover.content_for(path: path, line: line+1, column: column+1)
if content
range = content.location.yield_self do |location|
start_position = { line: location.line - 1, character: location.column }
end_position = { line: location.last_line - 1, character: location.last_column }
{ start: start_position, end: end_position }
end

LSP::Interface::Hover.new(
contents: { kind: "markdown", value: format_hover(content) },
range: range
)
LSP::Interface::Hover.new(
contents: { kind: "markdown", value: format_hover(content) },
range: range
)
end
end
rescue Typing::UnknownNodeError => exn
Steep.log_error exn, message: "Failed to compute hover: #{exn.inspect}"
Expand Down Expand Up @@ -126,33 +128,35 @@ def #{content.method_name}: #{content.method_type}

def response_to_completion(path:, line:, column:, trigger:)
Steep.logger.tagged("#response_to_completion") do
Steep.logger.info "path: #{path}, line: #{line}, column: #{column}, trigger: #{trigger}"

target = project.targets.find {|target| target.source_file?(path) } or return
target.type_check(target_sources: [], validate_signatures: false)

case (status = target&.status)
when Project::Target::TypeCheckStatus
subtyping = status.subtyping
source = target.source_files[path]
Steep.measure "Generating response" do
Steep.logger.info "path: #{path}, line: #{line}, column: #{column}, trigger: #{trigger}"

target = project.target_for_source_path(path) or return
target.type_check(target_sources: [], validate_signatures: false)

case (status = target&.status)
when Project::Target::TypeCheckStatus
subtyping = status.subtyping
source = target.source_files[path]

provider = Project::CompletionProvider.new(source_text: source.content, path: path, subtyping: subtyping)
items = begin
provider.run(line: line, column: column)
rescue Parser::SyntaxError
[]
end

completion_items = items.map do |item|
format_completion_item(item)
end

provider = Project::CompletionProvider.new(source_text: source.content, path: path, subtyping: subtyping)
items = begin
provider.run(line: line, column: column)
rescue Parser::SyntaxError
[]
end
Steep.logger.debug "items = #{completion_items.inspect}"

completion_items = items.map do |item|
format_completion_item(item)
LSP::Interface::CompletionList.new(
is_incomplete: false,
items: completion_items
)
end

Steep.logger.debug "items = #{completion_items.inspect}"

LSP::Interface::CompletionList.new(
is_incomplete: false,
items: completion_items
)
end
end
end
Expand Down
Loading

0 comments on commit d118b5e

Please sign in to comment.