Skip to content

Commit

Permalink
Support types from default args that are constants
Browse files Browse the repository at this point in the history
  • Loading branch information
Vici37 committed Dec 14, 2024
1 parent 3aa32ea commit cb15269
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 9 deletions.
19 changes: 19 additions & 0 deletions spec/compiler/apply_types_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,25 @@ describe Crystal::SourceTyper do
OUTPUT
end

it "types args with constant defaults" do
run_source_typer_spec(<<-INPUT, <<-OUTPUT, line_number: -1)
class Foo
MY_CONSTANT = 3
def test(arg = MY_CONSTANT); end
end
Foo.new.test(3.0)
INPUT
class Foo
MY_CONSTANT = 3

def test(arg : Float64 | Int32 = MY_CONSTANT) : Nil; end
end

Foo.new.test(3.0)
OUTPUT
end

it "doesn't type methods that are inherited" do
run_source_typer_spec(<<-INPUT, nil, line_number: -1)
class Foo
Expand Down
10 changes: 8 additions & 2 deletions src/compiler/crystal/command/typer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Crystal::Command
progress = false
error_trace = false

OptionParser.parse(options) do |opts|
parser = OptionParser.new do |opts|
opts.banner = <<-USAGE
Usage: crystal tool apply-types [options] entrypoint [def_locator [def_locator [...]]]
Expand Down Expand Up @@ -69,7 +69,13 @@ class Crystal::Command
end
end

entrypoint = options.shift
parser.parse(options)

unless entrypoint = options.shift?
puts parser
exit
end

def_locators = options

results = SourceTyper.new(
Expand Down
21 changes: 14 additions & 7 deletions src/compiler/crystal/tools/typer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ module Crystal
# And now infer types of everything
semantic_node = program.semantic nodes, cleanup: true

# We might run semantic later in an attempt to resolve defaults, don't display those stats or progress
@program.progress_tracker.stats = false
@program.progress_tracker.progress = false

# Use the DefVisitor to locate and match any 'def's that match a def_locator
def_visitor = DefVisitor.new(@def_locators, @excludes, entrypoint)
semantic_node.accept(def_visitor)
Expand Down Expand Up @@ -264,13 +268,6 @@ 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 All @@ -281,6 +278,16 @@ module Crystal
end
end

parsed.args.each do |arg|
if def_val = arg.default_value
if def_val.to_s.matches?(/^[A-Z_]+$/)
# This looks like a constant, let's try qualifying it with the parent type
def_val = Crystal::Path.new([parsed.owner.to_s, def_val.to_s])
end
all_typed_args[arg.external_name] << program.semantic(def_val).type rescue nil
end
end

# If a given collection of def_instances has a splat defined AND at least one def_instance didn't have a type for it,
# then we can't add types to the signature.
# https://crystal-lang.org/reference/1.14/syntax_and_semantics/type_restrictions.html#splat-type-restrictions
Expand Down

0 comments on commit cb15269

Please sign in to comment.