Skip to content
Draft
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
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ PATH
specs:
typeprof (0.31.1)
prism (>= 1.4.0)
rbs (>= 3.6.0)
rbs (>= 4.0.0.dev)

GEM
remote: https://rubygems.org/
Expand Down
29 changes: 28 additions & 1 deletion lib/typeprof/core/ast.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,41 @@ def self.parse_rb(path, src)
raise unless raw_scope.type == :program_node

prism_source = result.source
file_context = FileContext.new(path, prism_source, result.comments)
inline_members = build_inline_member_lookup(path, src, result)
file_context = FileContext.new(path, prism_source, inline_members)

cref = CRef::Toplevel
lenv = LocalEnv.new(file_context, cref, {}, [])

ProgramNode.new(raw_scope, lenv)
end

def self.build_inline_member_lookup(path, src, prism_result)
buffer = RBS::Buffer.new(name: Pathname(path), content: src)
inline_result = RBS::InlineParser.parse(buffer, prism_result)
lookup = {}
collect_def_members(inline_result.declarations, lookup)
lookup
rescue => _e
nil
end

def self.collect_def_members(decls_or_members, lookup)
decls_or_members.each do |entry|
case entry
when RBS::AST::Ruby::Members::DefMember
lookup[entry.node.object_id] = entry unless entry.method_type.empty?
when RBS::AST::Ruby::Members::AttrReaderMember,
RBS::AST::Ruby::Members::AttrWriterMember,
RBS::AST::Ruby::Members::AttrAccessorMember
lookup[entry.node.object_id] = entry if entry.type
end
if entry.respond_to?(:members)
collect_def_members(entry.members, lookup)
end
end
end

#: (untyped, TypeProf::Core::LocalEnv, ?bool, ?bool) -> TypeProf::Core::AST::Node
def self.create_node(raw_node, lenv, use_result = true, allow_meta = false)
while true
Expand Down
67 changes: 61 additions & 6 deletions lib/typeprof/core/ast/meta.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,26 @@ def initialize(raw_node, lenv)
raw_node.arguments.arguments.each do |raw_arg|
@args << raw_arg.value.to_sym if raw_arg.type == :symbol_node
end
# TODO: error for non-LIT
# TODO: fine-grained hover

@rbs_method_type = nil
inline_members = lenv.file_context.inline_members
if inline_members
member = inline_members[raw_node.object_id]
if member.is_a?(RBS::AST::Ruby::Members::AttrReaderMember) && member.type
rbs_method_type = RBS::MethodType.new(
type: RBS::Types::Function.empty(member.type),
type_params: [],
block: nil,
location: member.type.location
)
@rbs_method_type = AST.create_rbs_func_type(rbs_method_type, [], nil, lenv)
end
end
end

attr_reader :args
attr_reader :args, :rbs_method_type

def subnodes = { rbs_method_type: }
def attrs = { args: }

def req_positionals = []
Expand All @@ -77,6 +91,9 @@ def mname_code_range(name)

def install0(genv)
@args.each do |arg|
if @rbs_method_type
@changes.add_method_decl_box(genv, @lenv.cref.cpath, false, arg, [@rbs_method_type], false)
end
ivar_name = :"@#{ arg }"
ivar_box = @changes.add_ivar_read_box(genv, @lenv.cref.cpath, false, ivar_name)
ret_box = @changes.add_escape_box(genv, ivar_box.ret)
Expand All @@ -93,12 +110,43 @@ def initialize(raw_node, lenv)
raw_node.arguments.arguments.each do |raw_arg|
@args << raw_arg.value.to_sym if raw_arg.type == :symbol_node
end
# TODO: error for non-LIT
# TODO: fine-grained hover

@rbs_reader_method_type = nil
@rbs_writer_method_type = nil
inline_members = lenv.file_context.inline_members
if inline_members
member = inline_members[raw_node.object_id]
if member.is_a?(RBS::AST::Ruby::Members::AttrAccessorMember) && member.type
reader_rbs = RBS::MethodType.new(
type: RBS::Types::Function.empty(member.type),
type_params: [],
block: nil,
location: member.type.location
)
@rbs_reader_method_type = AST.create_rbs_func_type(reader_rbs, [], nil, lenv)
writer_rbs = RBS::MethodType.new(
type: RBS::Types::Function.new(
required_positionals: [RBS::Types::Function::Param.new(name: nil, type: member.type, location: member.type.location)],
optional_positionals: [],
rest_positionals: nil,
trailing_positionals: [],
required_keywords: {},
optional_keywords: {},
rest_keywords: nil,
return_type: member.type
),
type_params: [],
block: nil,
location: member.type.location
)
@rbs_writer_method_type = AST.create_rbs_func_type(writer_rbs, [], nil, lenv)
end
end
end

attr_reader :args
attr_reader :args, :rbs_reader_method_type, :rbs_writer_method_type

def subnodes = { rbs_reader_method_type:, rbs_writer_method_type: }
def attrs = { args: }

def mname_code_range(name)
Expand Down Expand Up @@ -134,6 +182,13 @@ def undefine0(genv)

def install0(genv)
@args.zip(@static_ret) do |arg, ive|
if @rbs_reader_method_type
@changes.add_method_decl_box(genv, @lenv.cref.cpath, false, arg, [@rbs_reader_method_type], false)
end
if @rbs_writer_method_type
@changes.add_method_decl_box(genv, @lenv.cref.cpath, false, :"#{ arg }=", [@rbs_writer_method_type], false)
end

ivar_box = @changes.add_ivar_read_box(genv, @lenv.cref.cpath, false, :"@#{ arg }")
ret_box = @changes.add_escape_box(genv, ivar_box.ret)
@changes.add_method_def_box(genv, @lenv.cref.cpath, false, arg, FormalArguments::Empty, [ret_box])
Expand Down
38 changes: 13 additions & 25 deletions lib/typeprof/core/ast/method.rb
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
module TypeProf::Core
class AST
def self.get_rbs_comment_before(raw_node, lenv)
comments = lenv.file_context.comments
return nil unless comments
node_line = raw_node.location.start_line
last_comment_index = comments.bsearch_index {|c| c.location.start_line >= node_line } || comments.size
rbs_comments = []
expected_line = node_line - 1
(last_comment_index - 1).downto(0) do |i|
comment = comments[i]
break unless comment.location.start_line == expected_line && comment.location.slice.start_with?("#:")
comment_loc = comment.location
comment_text = comment_loc.slice
rbs_comments[comment_loc.start_line - 1] = " " * (comment_loc.start_column + 2) + comment_text[2..]
expected_line -= 1
end
return nil if rbs_comments.empty?
rbs_comments = rbs_comments.map {|line| line || "" }.join("\n")
method_type = RBS::Parser.parse_method_type(rbs_comments)
if method_type
AST.create_rbs_func_type(method_type, method_type.type_params, method_type.block, lenv)
end
rescue RBS::ParsingError
# TODO: report the error
nil
def self.get_rbs_method_type(raw_node, lenv)
inline_members = lenv.file_context.inline_members
return nil unless inline_members

member = inline_members[raw_node.object_id]
return nil unless member

overload = member.overloads.first
return nil unless overload

method_type = overload.method_type
AST.create_rbs_func_type(method_type, method_type.type_params, method_type.block, lenv)
end

def self.parse_params(tbl, raw_args, lenv)
Expand Down Expand Up @@ -140,7 +128,7 @@ def initialize(raw_node, lenv, use_result)
raw_args = raw_node.parameters
raw_body = raw_node.body

@rbs_method_type = AST.get_rbs_comment_before(raw_node, lenv)
@rbs_method_type = AST.get_rbs_method_type(raw_node, lenv)

@singleton = singleton
@mid = mid
Expand Down
6 changes: 3 additions & 3 deletions lib/typeprof/core/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,12 @@ class Hash[K, V]
end

class FileContext
attr_reader :path, :comments
def initialize(path, prism_source = nil, comments = nil)
attr_reader :path, :inline_members
def initialize(path, prism_source = nil, inline_members = nil)
@path = path
@prism_source = prism_source
@code_units_cache = nil
@comments = comments
@inline_members = inline_members
end

def code_units_cache
Expand Down
20 changes: 12 additions & 8 deletions scenario/args/positionals_rbs_to_rb.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
## update
#: (*Integer) -> Integer
def foo(x, y)
x + y
class C
#: (*Integer) -> Integer
def foo(x, y)
x + y
end
end

## assert
class Object
class C
def foo: (Integer, Integer) -> Integer
end

## update
#: (*Integer) -> String
def foo(x, y)
x + y
class C
#: (*Integer) -> String
def foo(x, y)
x + y
end
end

## diagnostics
(3,2)-(3,7): expected: String; actual: Integer
(4,4)-(4,9): expected: String; actual: Integer
54 changes: 31 additions & 23 deletions scenario/rbs/check-block-return-bot.rb
Original file line number Diff line number Diff line change
@@ -1,32 +1,40 @@
## update
# Instance type (typecheck_for_module): pure bot
#: { () -> String } -> void
def yield_instance
yield
end
class C
# Instance type (typecheck_for_module): pure bot
#: { () -> String } -> void
def yield_instance
yield
end

yield_instance do
next "hello"
end
def test_instance
yield_instance do
next "hello"
end
end

# Bool type (SigTyBaseBoolNode): pure bot
#: { () -> bool } -> void
def yield_bool
yield
end
# Bool type (SigTyBaseBoolNode): pure bot
#: { () -> bool } -> void
def yield_bool
yield
end

yield_bool do
next false
end
def test_bool
yield_bool do
next false
end
end

# Nil type (SigTyBaseNilNode): pure bot
#: { () -> nil } -> void
def yield_nil
yield
end
# Nil type (SigTyBaseNilNode): pure bot
#: { () -> nil } -> void
def yield_nil
yield
end

yield_nil do
next nil
def test_nil
yield_nil do
next nil
end
end
end

## diagnostics
26 changes: 15 additions & 11 deletions scenario/rbs/check-block-return.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
## update: test0.rb
#: { () -> Integer } -> void
def foo
yield
end
class C
#: { () -> Integer } -> void
def foo
yield
end

foo do
next
def test
foo do
next

next "str"
next "str"

1.0
1.0
end
end
end

## diagnostics: test0.rb
(7,2)-(7,6): expected: Integer; actual: nil
(9,2)-(9,12): expected: Integer; actual: String
(11,2)-(11,5): expected: Integer; actual: Float
(9,6)-(9,10): expected: Integer; actual: nil
(11,6)-(11,16): expected: Integer; actual: String
(13,6)-(13,9): expected: Integer; actual: Float
Loading