diff --git a/lib/typeprof/core/ast/call.rb b/lib/typeprof/core/ast/call.rb index de6a7e5f..127ed382 100644 --- a/lib/typeprof/core/ast/call.rb +++ b/lib/typeprof/core/ast/call.rb @@ -31,7 +31,7 @@ def initialize(raw_node, recv, mid, mid_code_range, raw_args, last_arg, raw_bloc args << raw_arg.expression @splat_flags << true when Prism::ForwardingArgumentsNode - @forwarding_arguments = true + @forwarding_arguments = :rest else args << raw_arg @splat_flags << false @@ -113,10 +113,22 @@ def install0(genv) end if @forwarding_arguments - forward_a_args = (@lenv.forward_args || raise).to_actual_arguments(genv, @changes, self) - positional_args = forward_a_args.positionals - splat_flags = forward_a_args.splat_flags - keyword_args = forward_a_args.keywords + forward_a_args = (@lenv.forward_args || raise).to_actual_arguments( + genv, + @changes, + self, + include_leading_positionals: @forwarding_arguments != :rest, + activation_required: @forwarding_arguments == :rest, + ) + leading_args = @positional_args.map do |arg| + if arg.is_a?(DummyNilNode) + @lenv.get_var(:"*anonymous_rest") + else + arg.install(genv) + end + end + a_args = forward_a_args.prepend_positionals(leading_args, @splat_flags) + a_args = a_args.with_keywords(@keyword_args.install(genv)) if @keyword_args else positional_args = @positional_args.map do |arg| if arg.is_a?(DummyNilNode) @@ -125,8 +137,7 @@ def install0(genv) arg.install(genv) end end - splat_flags = @splat_flags - keyword_args = @keyword_args ? @keyword_args.install(genv) : nil + a_args = ActualArguments.new(positional_args, @splat_flags, @keyword_args ? @keyword_args.install(genv) : nil, nil) end if @block_body @@ -198,7 +209,11 @@ def install0(genv) blk_ty = forward_a_args.block end - a_args = ActualArguments.new(positional_args, splat_flags, keyword_args, blk_ty) + if @forwarding_arguments + a_args = a_args.with_block(blk_ty, omittable: !@block_body && !@block_pass && !@anonymous_block_forwarding) + else + a_args = a_args.with_block(blk_ty) + end box = @changes.add_method_call_box(genv, recv, @mid, a_args, !@recv) block_body = @block_body @@ -317,7 +332,7 @@ class ForwardingSuperNode < CallBaseNode def initialize(raw_node, lenv) raw_args = nil raw_block = raw_node.block - super(raw_node, nil, :"*super", nil, raw_args, nil, raw_block, lenv, forwarding_arguments: true) + super(raw_node, nil, :"*super", nil, raw_args, nil, raw_block, lenv, forwarding_arguments: :all) end end diff --git a/lib/typeprof/core/ast/method.rb b/lib/typeprof/core/ast/method.rb index 3ab7b0d8..bb2378ca 100644 --- a/lib/typeprof/core/ast/method.rb +++ b/lib/typeprof/core/ast/method.rb @@ -271,21 +271,22 @@ def install0(genv) block = @body.lenv.new_var(:"*given_block", self) end - forward_opt_positionals = @opt_positionals.map do - elem_vtx = Vertex.new(self) - [Source.new(genv.gen_ary_type(elem_vtx)), elem_vtx] - end + forward_opt_positionals = @opt_positionals.map { Vertex.new(self) } + forward_rest_positionals = @rest_positionals ? Vertex.new(self) : nil forward_opt_keywords = @opt_keywords.map {|_name| Vertex.new(self) } + forward_rest_keywords = @rest_keywords ? Vertex.new(self) : nil + forward_block = Vertex.new(self) + forward_activation = Vertex.new(self) @body.lenv.forward_args = ForwardingArguments.new( req_positionals, - forward_opt_positionals.map(&:first), - forward_opt_positionals.map(&:last), - rest_positionals, + forward_opt_positionals, + forward_rest_positionals, post_positionals, @req_keywords.zip(req_keywords), @opt_keywords.zip(forward_opt_keywords), - rest_keywords, - block, + forward_rest_keywords, + forward_block, + forward_activation, ) if @body diff --git a/lib/typeprof/core/env/method.rb b/lib/typeprof/core/env/method.rb index f7746e00..e1b31d9d 100644 --- a/lib/typeprof/core/env/method.rb +++ b/lib/typeprof/core/env/method.rb @@ -48,10 +48,33 @@ def with_keywords_as_last_positional_hash @positionals + [@keywords], @splat_flags + [false], nil, - @block + @block, ) end + def prepend_positionals(positionals, splat_flags) + return self if positionals.empty? + + ActualArguments.new(positionals + @positionals, splat_flags + @splat_flags, @keywords, @block) + end + + def with_keywords(keywords) + ActualArguments.new(@positionals, @splat_flags, keywords, @block) + end + + def with_block(block, omittable: false) + ActualArguments.new(@positionals, @splat_flags, @keywords, block) + end + + def add_box_edges(genv, box) + @keywords.add_edge(genv, box) if @keywords + @block.add_edge(genv, box) if @block + end + + def normalize_for_method_call(_genv) + self + end + def get_rest_args(genv, changes, start_rest, end_rest) vtxs = [] @@ -95,45 +118,175 @@ def get_keyword_arg(genv, changes, name) end end + class ForwardingActualArguments < ActualArguments + def initialize(positionals, splat_flags, keywords, block, positionals_omittable, keywords_omittable, block_omittable, activation, activation_required) + super(positionals, splat_flags, keywords, block) + @positionals_omittable = positionals_omittable + @keywords_omittable = keywords_omittable + @block_omittable = block_omittable + @activation = activation + @activation_required = activation_required + end + + attr_reader :positionals_omittable, :keywords_omittable, :block_omittable + + def new_vertexes(genv, node) + positionals = @positionals.map {|arg| arg.new_vertex(genv, node) } + splat_flags = @splat_flags + keywords = @keywords ? @keywords.new_vertex(genv, node) : nil + block = @block ? @block.new_vertex(genv, node) : nil + activation = @activation.new_vertex(genv, node) + ForwardingActualArguments.new(positionals, splat_flags, keywords, block, @positionals_omittable, @keywords_omittable, @block_omittable, activation, @activation_required) + end + + def with_keywords_as_last_positional_hash + return self unless @keywords + + ForwardingActualArguments.new( + @positionals + [@keywords], + @splat_flags + [false], + nil, + @block, + @positionals_omittable + [false], + false, + @block_omittable, + @activation, + @activation_required, + ) + end + + def prepend_positionals(positionals, splat_flags) + return self if positionals.empty? + + ForwardingActualArguments.new( + positionals + @positionals, + splat_flags + @splat_flags, + @keywords, + @block, + ::Array.new(positionals.size, false) + @positionals_omittable, + @keywords_omittable, + @block_omittable, + @activation, + @activation_required, + ) + end + + def with_keywords(keywords, keywords_omittable: false) + ForwardingActualArguments.new( + @positionals, + @splat_flags, + keywords, + @block, + @positionals_omittable, + keywords_omittable, + @block_omittable, + @activation, + @activation_required, + ) + end + + def with_block(block, omittable: false) + ForwardingActualArguments.new( + @positionals, + @splat_flags, + @keywords, + block, + @positionals_omittable, + @keywords_omittable, + omittable, + @activation, + @activation_required, + ) + end + + def add_box_edges(genv, box) + @activation.add_edge(genv, box) + super + end + + def normalize_for_method_call(genv) + if @activation.types.empty? + return nil if @activation_required + return self + end + + positionals = [] + splat_flags = [] + + @positionals.each_with_index do |arg, i| + unless @positionals_omittable[i] && @splat_flags[i] && empty_omittable_splat_argument?(genv, arg) + positionals << arg + splat_flags << @splat_flags[i] + end + end + + keywords = @keywords + keywords = nil if @keywords_omittable && keywords && keywords.types.empty? + + block = @block + block = nil if @block_omittable && block && block.types.empty? + + ActualArguments.new(positionals, splat_flags, keywords, block) + end + + private + + def empty_omittable_splat_argument?(genv, arg) + empty = true + arg.each_type do |ty| + ty = ty.base_type(genv) + unless ty.is_a?(Type::Instance) && ty.mod == genv.mod_ary && ty.args[0] && ty.args[0].types.empty? + empty = false + break + end + end + empty + end + end + class ForwardingArguments - def initialize(req_positionals, opt_positionals, opt_positional_elems, rest_positionals, post_positionals, req_keyword_pairs, opt_keyword_pairs, rest_keywords, block) + def initialize(req_positionals, opt_positionals, rest_positionals, post_positionals, req_keyword_pairs, opt_keyword_pairs, rest_keywords, block, activation) @req_positionals = req_positionals @opt_positionals = opt_positionals - @opt_positional_elems = opt_positional_elems @rest_positionals = rest_positionals @post_positionals = post_positionals @req_keyword_pairs = req_keyword_pairs @opt_keyword_pairs = opt_keyword_pairs @rest_keywords = rest_keywords @block = block + @activation = activation end - attr_reader :block - - def to_actual_arguments(genv, changes, node) - positionals = @req_positionals.dup + def to_actual_arguments(genv, changes, node, include_leading_positionals: true, activation_required: false) + positionals = include_leading_positionals ? @req_positionals.dup : [] splat_flags = ::Array.new(positionals.size, false) + positionals_omittable = ::Array.new(positionals.size, false) - @opt_positionals.each do |arg| - positionals << arg + @opt_positionals.each do |elem_vtx| + positionals << Source.new(genv.gen_ary_type(elem_vtx)) splat_flags << true + positionals_omittable << true end if @rest_positionals - positionals << @rest_positionals + positionals << Source.new(genv.gen_ary_type(@rest_positionals)) splat_flags << true + positionals_omittable << true end @post_positionals.each do |arg| positionals << arg splat_flags << false + positionals_omittable << false end - keywords = build_keyword_args(genv, changes, node) - ActualArguments.new(positionals, splat_flags, keywords, @block) + keywords, keywords_omittable = build_keyword_args(genv, changes, node) + ForwardingActualArguments.new(positionals, splat_flags, keywords, @block, positionals_omittable, keywords_omittable, true, @activation, activation_required) end def accept_actual_arguments(genv, changes, a_args) + changes.add_edge(genv, Source.new(genv.true_type), @activation) + if a_args.splat_flags.any? start_rest = [a_args.splat_flags.index(true), @req_positionals.size + @opt_positionals.size].min end_rest = [a_args.splat_flags.rindex(true) + 1, a_args.positionals.size - @post_positionals.size].max @@ -149,7 +302,7 @@ def accept_actual_arguments(genv, changes, a_args) end end - @opt_positional_elems.each_with_index do |elem_vtx, i| + @opt_positionals.each_with_index do |elem_vtx, i| i += @req_positionals.size if i < start_rest changes.add_edge(genv, a_args.positionals[i], elem_vtx) @@ -171,6 +324,12 @@ def accept_actual_arguments(genv, changes, a_args) end end + if @rest_positionals + rest_vtxs.each do |vtx| + changes.add_edge(genv, vtx, @rest_positionals) + end + end + else @req_positionals.each_with_index do |f_vtx, i| changes.add_edge(genv, a_args.positionals[i], f_vtx) @@ -184,11 +343,17 @@ def accept_actual_arguments(genv, changes, a_args) start_rest = @req_positionals.size end_rest = a_args.positionals.size - @post_positionals.size i = 0 - while i < @opt_positional_elems.size && start_rest < end_rest - changes.add_edge(genv, a_args.positionals[start_rest], @opt_positional_elems[i]) + while i < @opt_positionals.size && start_rest < end_rest + changes.add_edge(genv, a_args.positionals[start_rest], @opt_positionals[i]) i += 1 start_rest += 1 end + + if @rest_positionals + start_rest.upto(end_rest - 1) do |i| + changes.add_edge(genv, a_args.positionals[i], @rest_positionals) + end + end end changes.add_edge(genv, a_args.block, @block) if @block && a_args.block @@ -222,8 +387,11 @@ def accept_actual_arguments(genv, changes, a_args) private def build_keyword_args(genv, changes, node) - return nil if @req_keyword_pairs.empty? && @opt_keyword_pairs.empty? && !@rest_keywords - return @rest_keywords if @req_keyword_pairs.empty? && @opt_keyword_pairs.empty? + opt_keyword_pairs = @opt_keyword_pairs.reject {|_name, vtx| vtx.types.empty? } + + if @req_keyword_pairs.empty? && opt_keyword_pairs.empty? + return @rest_keywords, !!@rest_keywords + end unified_key = Vertex.new(node) unified_val = Vertex.new(node) @@ -235,18 +403,19 @@ def build_keyword_args(genv, changes, node) literal_pairs[name] = vtx end - @opt_keyword_pairs.each do |name, vtx| + opt_keyword_pairs.each do |name, vtx| changes.add_edge(genv, Source.new(Type::Symbol.new(genv, name)), unified_key) changes.add_edge(genv, vtx, unified_val) + literal_pairs[name] = vtx end base_hash_type = genv.gen_hash_type(unified_key, unified_val) changes.add_hash_splat_box(genv, @rest_keywords, unified_key, unified_val) if @rest_keywords if literal_pairs.empty? - Source.new(base_hash_type) + [Source.new(base_hash_type), false] else - Source.new(Type::Record.new(genv, literal_pairs, base_hash_type)) + [Source.new(Type::Record.new(genv, literal_pairs, base_hash_type)), false] end end end diff --git a/lib/typeprof/core/graph/box.rb b/lib/typeprof/core/graph/box.rb index c486bec7..0918e033 100644 --- a/lib/typeprof/core/graph/box.rb +++ b/lib/typeprof/core/graph/box.rb @@ -1009,8 +1009,7 @@ def initialize(node, genv, recv, mid, a_args, subclasses, suppress_errors: false @recv.add_edge(genv, self) @mid = mid @a_args = a_args.new_vertexes(genv, node) - @a_args.keywords.add_edge(genv, self) if @a_args.keywords - @a_args.block.add_edge(genv, self) if @a_args.block + @a_args.add_box_edges(genv, self) @ret = Vertex.new(node) @subclasses = subclasses @suppress_errors = suppress_errors @@ -1020,6 +1019,9 @@ def initialize(node, genv, recv, mid, a_args, subclasses, suppress_errors: false attr_reader :recv, :mid, :ret def run0(genv, changes) + a_args = @a_args.normalize_for_method_call(genv) + return unless a_args + edges = Set.empty called_mdefs = Set.empty error_count = 0 @@ -1032,7 +1034,7 @@ def run0(genv, changes) end end error_count += 1 - elsif me.builtin && me.builtin[changes, @node, orig_ty, @a_args, @ret] + elsif me.builtin && me.builtin[changes, @node, orig_ty, a_args, @ret] # do nothing elsif !me.decls.empty? # TODO: support "| ..." @@ -1045,7 +1047,7 @@ def run0(genv, changes) ty_env[param] = arg || (default_ty ? default_ty.covariant_vertex(genv, changes, ty_env) : Source.new) end end - mdecl.resolve_overloads(changes, genv, @node, ty_env, @a_args, @ret) do |method_type| + mdecl.resolve_overloads(changes, genv, @node, ty_env, a_args, @ret) do |method_type| @generics[method_type] ||= method_type.type_params.map { Vertex.new(@node) } end end @@ -1053,7 +1055,7 @@ def run0(genv, changes) me.defs.each do |mdef| next if called_mdefs.include?(mdef) called_mdefs << mdef - mdef.call(changes, genv, @a_args, @ret) + mdef.call(changes, genv, a_args, @ret) end else pp me @@ -1066,7 +1068,7 @@ def run0(genv, changes) me.defs.each do |mdef| next if called_mdefs.include?(mdef) called_mdefs << mdef - mdef.call(changes, genv, @a_args, @ret) + mdef.call(changes, genv, a_args, @ret) end end end diff --git a/scenario/args/forwarding_arguments.rb b/scenario/args/forwarding_arguments.rb index 43090449..7eb580e0 100644 --- a/scenario/args/forwarding_arguments.rb +++ b/scenario/args/forwarding_arguments.rb @@ -25,6 +25,85 @@ def foo(...) bar(...) end +def bar + 1 +end + +foo() + +## assert +class Object + def foo: (*untyped, **untyped) -> Integer + def bar: -> Integer +end + +## update +def foo(x, ...) + bar(x, ...) +end + +def bar(a, b) + [a, b] +end + +foo(1, 2) + +## assert +class Object + def foo: (Integer, *Integer, **untyped) -> [Integer, Integer] + def bar: (Integer, Integer) -> [Integer, Integer] +end + +## update +def foo(...) + bar(...) +end + +def bar +end + +## diagnostics + +## update +def foo(...) + bar(1, ...) +end + +def bar(a, *b, **c) + [a, b, c] +end + +foo(x: 4, y: 5) + +## assert +class Object + def foo: (*untyped, **Integer) -> [Integer, Array[untyped], { x: Integer, y: Integer }] + def bar: (Integer, *untyped, **Integer) -> [Integer, Array[untyped], { x: Integer, y: Integer }] +end + +## update +def foo(...) + extra = "x" + bar(extra, ...) +end + +def bar(a, *b, **c) + [a, b, c] +end + +foo(2, x: 4, y: 5) + +## assert +class Object + def foo: (*Integer, **Integer) -> [String, Array[Integer], { x: Integer, y: Integer }] + def bar: (String, *Integer, **Integer) -> [String, Array[Integer], { x: Integer, y: Integer }] +end + +## update +def foo(...) + bar(...) +end + def bar(*a, **b) [a, b] end diff --git a/scenario/known-issues/forwarding-arguments-extra-positional.rb b/scenario/known-issues/forwarding-arguments-extra-positional.rb deleted file mode 100644 index a473812c..00000000 --- a/scenario/known-issues/forwarding-arguments-extra-positional.rb +++ /dev/null @@ -1,17 +0,0 @@ -## update -def foo(...) - extra = "x" - bar(extra, ...) -end - -def bar(a, *b, **c) - [a, b, c] -end - -foo(2, x: 4, y: 5) - -## assert -class Object - def foo: (*Integer, **Integer) -> [String, Array[Integer], { x: Integer, y: Integer }] - def bar: (String, *Integer, **Integer) -> [String, Array[Integer], { x: Integer, y: Integer }] -end diff --git a/scenario/misc/super.rb b/scenario/misc/super.rb index a2531a06..f434ba35 100644 --- a/scenario/misc/super.rb +++ b/scenario/misc/super.rb @@ -59,3 +59,95 @@ def foo: (*Integer, **Integer) -> [Array[Integer], { x: Integer, y: Integer }] class SuperChild < SuperBase def foo: (*Integer, **Integer) -> [Array[Integer], { x: Integer, y: Integer }] end + +## update +class SuperBase + def foo + 1 + end +end + +class SuperChild < SuperBase + def foo(...) + super(...) + end +end + +SuperChild.new.foo() + +## assert +class SuperBase + def foo: -> Integer +end +class SuperChild < SuperBase + def foo: (*untyped, **untyped) -> Integer +end + +## update +class SuperBase + def foo(a) + a + end +end + +class SuperChild < SuperBase + def foo(x, ...) + super(...) + end +end + +SuperChild.new.foo(1, 2) + +## assert +class SuperBase + def foo: (Integer) -> Integer +end +class SuperChild < SuperBase + def foo: (Integer, *Integer, **untyped) -> Integer +end + +## update +class SuperBase + def foo(a, *b) + [a, b] + end +end + +class SuperChild < SuperBase + def foo(...) + super(1, ...) + end +end + +SuperChild.new.foo() + +## assert +class SuperBase + def foo: (Integer, *untyped) -> [Integer, Array[untyped]] +end +class SuperChild < SuperBase + def foo: (*untyped, **untyped) -> [Integer, Array[untyped]] +end + +## update +class SuperBase + def foo(a, b) + [a, b] + end +end + +class SuperChild < SuperBase + def foo(x, ...) + super(x, ...) + end +end + +SuperChild.new.foo(1, 2) + +## assert +class SuperBase + def foo: (Integer, Integer) -> [Integer, Integer] +end +class SuperChild < SuperBase + def foo: (Integer, *Integer, **untyped) -> [Integer, Integer] +end