Skip to content
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
37 changes: 16 additions & 21 deletions lib/typeprof/core/ast/meta.rb
Original file line number Diff line number Diff line change
Expand Up @@ -250,25 +250,14 @@ def initialize(raw_node, members, kind, lenv)

attr_reader :static_cpath, :members, :kind, :block_body

# Not a valid constant name, so no Ruby code can refer to it
BASE_CNAME = :"<struct base>"

def struct_base_cpath = @static_cpath + [BASE_CNAME]

def subnodes = { block_body: }
def attrs = { static_cpath:, members:, kind: }

# A method written in the block body (e.g. a custom initialize)
# overrides the auto-generated one, which should then be hidden
# from the RBS output.
def block_defines_method?(singleton, mid)
return false unless @block_body
found = false
@block_body.traverse do |event, node|
if event == :enter && node.is_a?(DefNode) &&
node.singleton == singleton && node.mid == mid &&
node.lenv.cref.cpath == @static_cpath
found = true
end
end
found
end

# Interface expected by MethodDefBox
def req_positionals = @kind == :struct ? @members : []
def opt_positionals = []
Expand All @@ -283,8 +272,9 @@ def define0(genv)
mod = genv.resolve_cpath(@static_cpath)
# add_module_def internally calls get_const(name).add_def(self)
cdef = mod.add_module_def(genv, self)
genv.resolve_cpath(struct_base_cpath).add_module_def(genv, self)
@members.each do |member|
ive = genv.resolve_ivar(@static_cpath, false, member)
ive = genv.resolve_ivar(struct_base_cpath, false, member)
ive.add_def(self)
end
@block_body.define(genv) if @block_body
Expand All @@ -295,8 +285,11 @@ def define_copy(genv)
mod = genv.resolve_cpath(@static_cpath)
mod.add_module_def(genv, self)
mod.remove_module_def(genv, @prev_node)
base = genv.resolve_cpath(struct_base_cpath)
base.add_module_def(genv, self)
base.remove_module_def(genv, @prev_node)
@members.each do |member|
ive = genv.resolve_ivar(@static_cpath, false, member)
ive = genv.resolve_ivar(struct_base_cpath, false, member)
ive.add_def(self)
ive.remove_def(@prev_node)
end
Expand All @@ -306,8 +299,9 @@ 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)
@members.each do |member|
ive = genv.resolve_ivar(@static_cpath, false, member)
ive = genv.resolve_ivar(struct_base_cpath, false, member)
ive.remove_def(self)
end
@block_body.undefine(genv) if @block_body
Expand All @@ -320,7 +314,7 @@ def install0(genv)
@changes.add_edge(genv, mod_val, @static_ret.vtx)
end

cpath = @static_cpath
cpath = struct_base_cpath
@members.each do |member|
# Use bare `:member` (not `:@member`) so the slot can't collide with a
# user-written @member ivar — Struct/Data fields are not real ivars.
Expand Down Expand Up @@ -357,7 +351,8 @@ def install0(genv)

# Struct.[] is an alias for Struct.new
if @kind == :struct
self_ret = @changes.add_escape_box(genv, Source.new(Type::Instance.new(genv, genv.resolve_cpath(cpath), [])))
# 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

Expand Down
3 changes: 2 additions & 1 deletion lib/typeprof/core/env/module_entity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@ def find_superclass_const_read
when AST::ModuleNode
return nil
when AST::StructNewNode
return [] # inherits from Object (Struct < Object)
base_cpath = mdef.struct_base_cpath
return base_cpath == @cpath ? [] : base_cpath
else
raise
end
Expand Down
12 changes: 10 additions & 2 deletions lib/typeprof/core/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,9 @@ def completion(path, trigger, pos)
break
end
end
yield mid, "#{ mod.cpath.join("::" )}#{ singleton ? "." : "#" }#{ mid } : #{ sig }" if sig
# The base class has no name to show
owner = mod.cpath.last == AST::StructNewNode::BASE_CNAME ? base_ty.mod : mod
yield mid, "#{ owner.cpath.join("::" )}#{ singleton ? "." : "#" }#{ mid } : #{ sig }" if sig
end
end
end
Expand Down Expand Up @@ -481,6 +483,10 @@ def dump_declarations(path)
s = "class #{ format_declared_const_path(node.static_cpath, stack) }"
mod = @genv.resolve_cpath(node.static_cpath)
superclass = mod.superclass
# The base class is not shown; its members are printed inline
while superclass && superclass.cpath.last == AST::StructNewNode::BASE_CNAME
superclass = superclass.superclass
end
if superclass == nil
s << " # failed to identify its superclass"
elsif superclass.cpath != []
Expand All @@ -499,7 +505,9 @@ def dump_declarations(path)
end
# Output method definitions from meta nodes (StructNewNode etc.)
node.boxes(:mdef) do |mdef|
next if node.is_a?(AST::StructNewNode) && node.block_defines_method?(mdef.singleton, mdef.mid)
# A user-written method overrides the generated one
next if node.is_a?(AST::StructNewNode) &&
!@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
else
Expand Down
45 changes: 45 additions & 0 deletions scenario/incremental/struct-new-block.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## update
Dog = Struct.new(:name, :age)
Dog.new("fred", 5)

## assert
class Dog
def name: -> String
def name=: (untyped) -> untyped
def age: -> Integer
def age=: (untyped) -> untyped
def initialize: (String, Integer) -> void
def self.[]: (String, Integer) -> Dog
end

## update
Dog = Struct.new(:name, :age) do
def initialize(name, age)
super(name.to_s, age.to_i)
end
end
Dog.new("fred", "5")

## assert
class Dog
def name: -> String
def name=: (untyped) -> untyped
def age: -> Integer
def age=: (untyped) -> untyped
def self.[]: (String, Integer) -> Dog
def initialize: (String, String) -> void
end

## update
Dog = Struct.new(:name, :age)
Dog.new("fred", 5)

## assert
class Dog
def name: -> String
def name=: (untyped) -> untyped
def age: -> Integer
def age=: (untyped) -> untyped
def initialize: (String, Integer) -> void
def self.[]: (String, Integer) -> Dog
end
54 changes: 54 additions & 0 deletions scenario/misc/struct_new.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,57 @@ def y=: (untyped) -> untyped
def self.[]: (Integer, Integer) -> Pt
def initialize: (?Integer, ?Integer) -> void
end

## update
Dog = Struct.new(:name, :age) do
def initialize(name, age)
super(name.to_s, age.to_i)
end
end
Dog.new("fred", "5")

## assert
class Dog
def name: -> String
def name=: (untyped) -> untyped
def age: -> Integer
def age=: (untyped) -> untyped
def self.[]: (String, Integer) -> Dog
def initialize: (String, String) -> void
end

## update
D = Data.define(:name, :age) do
def initialize(name:, age:)
super(name: name.to_s, age: age.to_i)
end
end
D.new(name: "fred", age: "5")

## assert
class D
def name: -> String
def age: -> Integer
def initialize: (name: String, age: String) -> void
end

## update
Pt = Struct.new(:x, :y)
class Pt
def initialize(x = 0, y = 0)
super
end
end
Pt.new(3, 4)

## assert
class Pt
def x: -> Integer
def x=: (untyped) -> untyped
def y: -> Integer
def y=: (untyped) -> untyped
def self.[]: (Integer, Integer) -> Pt
end
class Pt
def initialize: (?Integer, ?Integer) -> void
end
Loading