Skip to content

Commit

Permalink
Support default types, don't add types to overridden methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Vici37 committed Jan 11, 2025
1 parent 75aa570 commit 3384800
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 6 deletions.
37 changes: 34 additions & 3 deletions spec/compiler/apply_types_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ def run_source_typer_spec(input, expected_output,

typer.files.to_a.should eq [entrypoint_file]
result = typer.type_source(entrypoint_file, input)
result.should_not be_nil
not_nil_result = result.not_nil!("Why is this failing???")
not_nil_result.strip.should eq expected_output
result.try(&.strip).should eq expected_output.try &.strip
end

describe Crystal::SourceTyper do
Expand Down Expand Up @@ -412,6 +410,39 @@ describe Crystal::SourceTyper do
OUTPUT
end

it "types args and include default type" do
run_source_typer_spec(<<-INPUT, <<-OUTPUT)
def test(arg = nil)
nil
end
test(3)
INPUT
def test(arg : Int32? = nil) : Nil
nil
end

test(3)
OUTPUT
end

it "doesn't type methods that are inherited" do
run_source_typer_spec(<<-INPUT, nil, line_number: -1)
class Foo
def test(arg)
nil
end
end
class Bar < Foo
def test(arg)
1
end
end
Bar.new.test(3)
INPUT
end

it "runs prelude and types everything" do
run_source_typer_spec(<<-INPUT, <<-OUTPUT, line_number: -1, prelude: "prelude")
# This file tries to capture each type of definition format
Expand Down
42 changes: 39 additions & 3 deletions src/compiler/crystal/tools/typer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,11 @@ module Crystal

program.types.each { |_, t| types << t }

overridden_method_locations = [] of String
while type = types.shift?
type.types?.try &.each { |_, t| types << t }
# pp! type, def_overrides_parent_def(type)
def_overrides_parent_def(type).each { |loc| overridden_method_locations << loc }

# Check for class instance 'def's
if type.responds_to?(:def_instances)
Expand All @@ -167,9 +170,38 @@ module Crystal
end
end

# Now remove all overridden methods
overridden_method_locations.each do |loc|
ret.delete(loc)
end

ret
end

private def def_overrides_parent_def(type) : Array(String)
overriden_locations = [] of String
type.defs.try &.each_value do |defs_with_metadata|
defs_with_metadata.each do |def_with_metadata|
next if def_with_metadata.def.location.to_s.starts_with?("expanded macro:")
type.ancestors.each do |ancestor|
other_defs_with_metadata = ancestor.defs.try &.[def_with_metadata.def.name]?
other_defs_with_metadata.try &.each do |other_def_with_metadata|
next if other_def_with_metadata.def.location.to_s.starts_with?("expanded macro:")
found_def_with_same_name = true

if def_with_metadata.compare_strictness(other_def_with_metadata, self_owner: type, other_owner: ancestor) == 0
# puts "Method #{type}##{def_with_metadata.def.name} overrides #{ancestor}##{def_with_metadata.def.name}"
# Found a method with the same name and same, stricter or weaker restriction,
# so it overrides
overriden_locations << def_with_metadata.def.location.to_s
end
end
end
end
end
overriden_locations
end

# Given an 'arg', return its type that's good for printing (VirtualTypes suffix themselves with a '+')
private def resolve_type(arg)
t = arg.type
Expand All @@ -188,10 +220,7 @@ module Crystal

# Generates a map of (parsed) Def#location => Signature for that Def
private def init_signatures(accepted_defs : Hash(String, Crystal::Def)) : Hash(String, Signature)
# This is hard to read, but transforms the def_instances array into a hash of def.location -> its full Signature
@_signatures ||= accepted_def_instances(accepted_defs).compact_map do |location, def_instances|
# Finally, combine all def_instances for a single def_obj_id into a single signature

parsed = accepted_defs[location]

all_typed_args = Hash(String, Set(Crystal::Type)).new { |h, k| h[k] = Set(Crystal::Type).new }
Expand Down Expand Up @@ -235,6 +264,13 @@ module Crystal
else
raise "Unknown handling of arg #{arg} at #{def_instance.location} in #{def_instance}\n#{parsed}"
end

# Special case - we can have default args that are never used be a different type than what was set.
# Ensure those default arg types also get respected (i.e. `arg = nil` => `arg : Int32? = nil` instead
# of `arg : Int32 = nil`)
if def_val = arg.default_value
all_typed_args[arg.external_name] << program.semantic(def_val).type
end
end

encountered_non_splat_arg_def_instance |= !encountered_splat_arg
Expand Down

0 comments on commit 3384800

Please sign in to comment.