diff --git a/lib/typeprof/core/ast/meta.rb b/lib/typeprof/core/ast/meta.rb index a219a184f..f3c2ff69b 100644 --- a/lib/typeprof/core/ast/meta.rb +++ b/lib/typeprof/core/ast/meta.rb @@ -305,8 +305,9 @@ def subnodes = { block_body: } def attrs = { static_cpath:, members:, kind: } # Interface expected by MethodDefBox - def req_positionals = @kind == :struct ? @members : [] - def opt_positionals = [] + def req_positionals = [] + # Struct.new(:x).new is valid, so every member is optional + def opt_positionals = @kind == :struct ? @members : [] def rest_positionals = nil def post_positionals = [] def req_keywords = @kind == :data ? @members : [] @@ -323,6 +324,10 @@ def define0(genv) ive = genv.resolve_ivar(struct_base_cpath, false, member) ive.add_def(self) end + if @kind == :struct + # Struct.[] calls the receiver's initialize, like Class#new + genv.resolve_method(struct_base_cpath, true, :[]).builtin = Builtin.new(genv).method(:class_new) + end @block_body.define(genv) if @block_body cdef end @@ -345,11 +350,16 @@ def define_copy(genv) def undefine0(genv) mod = genv.resolve_cpath(@static_cpath) mod.remove_module_def(genv, self) - genv.resolve_cpath(struct_base_cpath).remove_module_def(genv, self) + base = genv.resolve_cpath(struct_base_cpath) + base.remove_module_def(genv, self) @members.each do |member| ive = genv.resolve_ivar(struct_base_cpath, false, member) ive.remove_def(self) end + # On an update, the new node has already set the builtin + if base.module_defs.to_a.none? { _1.kind == :struct } + genv.resolve_method(struct_base_cpath, true, :[]).builtin = nil + end @block_body.undefine(genv) if @block_body end @@ -388,20 +398,13 @@ def install0(genv) end init_ret = @changes.add_escape_box(genv, Source.new(genv.nil_type)) if @kind == :struct - init_f_args = FormalArguments.new(init_vtxs, [], nil, [], [], [], nil, nil) + init_f_args = FormalArguments.new([], init_vtxs, nil, [], [], [], nil, nil) else # Data.define uses keyword arguments init_f_args = FormalArguments.new([], [], nil, [], init_vtxs, [], nil, nil) end @changes.add_method_def_box(genv, cpath, false, :initialize, init_f_args, [init_ret]) - # Struct.[] is an alias for Struct.new - if @kind == :struct - # Struct.[] builds the struct class itself, not the base class - self_ret = @changes.add_escape_box(genv, Source.new(Type::Instance.new(genv, genv.resolve_cpath(@static_cpath), []))) - @changes.add_method_def_box(genv, cpath, true, :[], init_f_args, [self_ret]) - end - # Install block body (additional method definitions) if @block_body @block_body.lenv.locals[:"*self"] = @block_body.lenv.cref.get_self(genv) diff --git a/lib/typeprof/core/graph/box.rb b/lib/typeprof/core/graph/box.rb index 68053326c..25cce3d97 100644 --- a/lib/typeprof/core/graph/box.rb +++ b/lib/typeprof/core/graph/box.rb @@ -840,7 +840,7 @@ def call(changes, genv, a_args, ret) end end - def show(output_parameter_names) + def show(output_parameter_names, ret: nil) block_show = [] if @record_block.used blk_f_args = @record_block.f_args.map {|arg| arg.show }.join(", ") @@ -891,7 +891,7 @@ def show(output_parameter_names) args = args.join(", ") s = args.empty? ? [] : ["(#{ args })"] s << "#{ block_show.sort.join(" | ") }" unless block_show.empty? - s << "-> #{ @mid == :initialize ? "void" : @ret.show }" + s << "-> #{ ret || (@mid == :initialize ? "void" : @ret.show) }" s.join(" ") end end diff --git a/lib/typeprof/core/service.rb b/lib/typeprof/core/service.rb index ed8077f9d..a71b44fea 100644 --- a/lib/typeprof/core/service.rb +++ b/lib/typeprof/core/service.rb @@ -520,6 +520,14 @@ def dump_declarations(path) !@genv.resolve_method(node.static_cpath, mdef.singleton, mdef.mid).defs.empty? out << " " * stack.size + "def #{ mdef.singleton ? "self." : "" }#{ mdef.mid }: " + mdef.show(@options[:output_parameter_names]) end + if node.is_a?(AST::StructNewNode) && node.kind == :struct && + @genv.resolve_method(node.static_cpath, true, :[]).defs.empty? + # Struct.[] takes the same arguments as the initialize it reaches + init_mdef = @genv.resolve_method(node.static_cpath, false, :initialize).defs.to_a.first || + @genv.resolve_method(node.struct_base_cpath, false, :initialize).defs.to_a.first + ret = Type::Instance.new(@genv, mod, []).show + out << " " * stack.size + "def self.[]: " + init_mdef.show(@options[:output_parameter_names], ret:) + end else stack.pop out << " " * stack.size + "end" diff --git a/scenario/diagnostics/struct-aref.rb b/scenario/diagnostics/struct-aref.rb new file mode 100644 index 000000000..c9e1aba81 --- /dev/null +++ b/scenario/diagnostics/struct-aref.rb @@ -0,0 +1,13 @@ +## update +Pt = Struct.new(:x, :y) do + def initialize(x = 0, y = 0) + super + end +end + +Pt[] +Pt[3, 4] +Pt[1, 2, 3] + +## diagnostics +(9,2)-(9,11): wrong number of arguments (3 for 0...2) diff --git a/scenario/diagnostics/struct-optional-members.rb b/scenario/diagnostics/struct-optional-members.rb new file mode 100644 index 000000000..edab19be3 --- /dev/null +++ b/scenario/diagnostics/struct-optional-members.rb @@ -0,0 +1,10 @@ +## update +Pt = Struct.new(:x, :y) + +Pt.new +Pt.new(1) +Pt[] +Pt[1, 2, 3] + +## diagnostics +(6,2)-(6,11): wrong number of arguments (3 for 0...2) diff --git a/scenario/incremental/struct-new-block.rb b/scenario/incremental/struct-new-block.rb index 4f5e606ee..f34057d30 100644 --- a/scenario/incremental/struct-new-block.rb +++ b/scenario/incremental/struct-new-block.rb @@ -8,8 +8,8 @@ def name: -> String def name=: (untyped) -> untyped def age: -> Integer def age=: (untyped) -> untyped - def initialize: (String, Integer) -> void - def self.[]: (String, Integer) -> Dog + def initialize: (?String, ?Integer) -> void + def self.[]: (?String, ?Integer) -> Dog end ## update @@ -26,7 +26,7 @@ def name: -> String def name=: (untyped) -> untyped def age: -> Integer def age=: (untyped) -> untyped - def self.[]: (String, Integer) -> Dog + def self.[]: (String, String) -> Dog def initialize: (String, String) -> void end @@ -40,6 +40,6 @@ def name: -> String def name=: (untyped) -> untyped def age: -> Integer def age=: (untyped) -> untyped - def initialize: (String, Integer) -> void - def self.[]: (String, Integer) -> Dog + def initialize: (?String, ?Integer) -> void + def self.[]: (?String, ?Integer) -> Dog end diff --git a/scenario/misc/struct_new.rb b/scenario/misc/struct_new.rb index af3f587c1..21df5dff6 100644 --- a/scenario/misc/struct_new.rb +++ b/scenario/misc/struct_new.rb @@ -12,8 +12,8 @@ def bar: -> Integer def bar=: (Integer) -> Integer def baz: -> String def baz=: (untyped) -> untyped - def initialize: (Integer, String) -> void - def self.[]: (Integer, String) -> Foo + def initialize: (?Integer, ?String) -> void + def self.[]: (?Integer, ?String) -> Foo end ## update @@ -41,8 +41,8 @@ def double class Bar def n: -> Integer def n=: (untyped) -> untyped - def initialize: (Integer) -> void - def self.[]: (Integer) -> Bar + def initialize: (?Integer) -> void + def self.[]: (?Integer) -> Bar def double: -> Integer end @@ -64,8 +64,8 @@ def ivar class Baz def v: -> Integer def v=: (untyped) -> untyped - def initialize: (Integer) -> void - def self.[]: (Integer) -> Baz + def initialize: (?Integer) -> void + def self.[]: (?Integer) -> Baz def set_label: -> String def ivar: -> String end @@ -88,7 +88,7 @@ def x: -> Integer def x=: (untyped) -> untyped def y: -> Integer def y=: (untyped) -> untyped - def self.[]: (Integer, Integer) -> Pt + def self.[]: (?Integer, ?Integer) -> Pt def initialize: (?Integer, ?Integer) -> void end @@ -106,7 +106,7 @@ def name: -> String def name=: (untyped) -> untyped def age: -> Integer def age=: (untyped) -> untyped - def self.[]: (String, Integer) -> Dog + def self.[]: (String, String) -> Dog def initialize: (String, String) -> void end @@ -140,7 +140,7 @@ def x: -> Integer def x=: (untyped) -> untyped def y: -> Integer def y=: (untyped) -> untyped - def self.[]: (Integer, Integer) -> Pt + def self.[]: (?Integer, ?Integer) -> Pt end class Pt def initialize: (?Integer, ?Integer) -> void