Skip to content

Commit

Permalink
Opt-in autocast for integers and floats (#11431)
Browse files Browse the repository at this point in the history
Co-authored-by: Sijawusz Pur Rahnama <[email protected]>
Co-authored-by: Ary Borenszweig <[email protected]>
  • Loading branch information
3 people authored Nov 18, 2021
1 parent a9ee264 commit 5b18256
Show file tree
Hide file tree
Showing 16 changed files with 303 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,16 @@ describe "Code gen: automatic cast" do
foo(255, 60)
)).to_i.should eq(255)
end

it "casts integer variable to larger type (#9565)" do
run(%(
def foo(x : Int64)
x
end
x = 123
foo(x)
),
flags: ["number_autocast"]).to_i64.should eq(123)
end
end
8 changes: 4 additions & 4 deletions spec/compiler/macro/macro_methods_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1558,15 +1558,15 @@ module Crystal
end
end

it "executes instance_vars on symbol literal" do
it "executes instance_vars on symbol type" do
assert_macro("{{x.instance_vars.map &.stringify}}", %([])) do |program|
{x: TypeNode.new(SymbolLiteralType.new(program, "foo".symbol))}
{x: TypeNode.new(program.symbol)}
end
end

it "executes class_vars on symbol literal" do
it "executes class_vars on symbol type" do
assert_macro("{{x.class_vars.map &.stringify}}", %([])) do |program|
{x: TypeNode.new(SymbolLiteralType.new(program, "foo".symbol))}
{x: TypeNode.new(program.symbol)}
end
end

Expand Down
120 changes: 119 additions & 1 deletion spec/compiler/semantic/automatic_cast_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,101 @@ describe "Semantic: automatic cast" do
end
Baz.new.as(Foo).foo(1)
)) { int64 }
)) { int64 }
end

it "casts integer variable to larger type (#9565)" do
assert_type(%(
def foo(x : Int64)
x
end
x = 1_i32
foo(x)
),
flags: "number_autocast") { int64 }
end

it "casts integer variable to larger type (Int64 to Int128) (#9565)" do
assert_type(%(
def foo(x : Int128)
x
end
x = 1_i64
foo(x)
),
flags: "number_autocast") { int128 }
end

it "casts integer expression to larger type (#9565)" do
assert_type(%(
def foo(x : Int64)
x
end
def bar
1_i32
end
foo(bar)
),
flags: "number_autocast") { int64 }
end

it "says ambiguous call for integer var to larger type (#9565)" do
assert_error %(
def foo(x : Int32)
x
end
def foo(x : Int64)
x
end
x = 1_u8
foo(x)
),
"ambiguous call, implicit cast of UInt8 matches all of Int32, Int64",
flags: "number_autocast"
end

it "says ambiguous call for integer var to union type (#9565)" do
assert_error %(
def foo(x : Int32 | UInt32)
x
end
x = 1_u8
foo(x)
),
"ambiguous call, implicit cast of UInt8 matches all of Int32, UInt32",
flags: "number_autocast"
end

it "can't cast integer to another type when it doesn't fit (#9565)" do
assert_error %(
def foo(x : Int32)
x
end
x = 1_i64
foo(x)
),
"no overload matches 'foo' with type Int64",
flags: "number_autocast"
end

it "doesn't cast integer variable to larger type (not #9565)" do
assert_error %(
def foo(x : Int64)
x
end
x = 1_i32
foo(x)
),
"no overload matches 'foo' with type Int32"
end

it "doesn't autocast number on union (#8655)" do
Expand All @@ -597,6 +691,30 @@ describe "Semantic: automatic cast" do
"ambiguous call, implicit cast of 255 matches all of UInt64, Int64"
end

it "autocasts integer variable to float type (#9565)" do
assert_type(%(
def foo(x : Float64)
x
end
x = 1_i32
foo(x)
),
flags: "number_autocast") { float64 }
end

it "autocasts float32 variable to float64 type (#9565)" do
assert_type(%(
def foo(x : Float64)
x
end
x = 1.0_f32
foo(x)
),
flags: "number_autocast") { float64 }
end

it "autocasts nested type from non-nested type (#10315)" do
assert_no_errors(%(
module Moo
Expand Down
6 changes: 3 additions & 3 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ def assert_expand_third(from : String, to, *, file = __FILE__, line = __LINE__)
assert_expand node, to, file: file, line: line
end

def assert_error(str, message = nil, *, inject_primitives = false, file = __FILE__, line = __LINE__)
def assert_error(str, message = nil, *, inject_primitives = false, file = __FILE__, line = __LINE__, flags = nil)
expect_raises TypeException, message, file, line do
semantic str, inject_primitives: inject_primitives
semantic str, inject_primitives: inject_primitives, flags: flags
end
end

Expand Down Expand Up @@ -221,7 +221,7 @@ class Crystal::SpecRunOutput
@output
end

delegate to_i, to_u64, to_f, to_f32, to_f64, to: @output
delegate to_i, to_i64, to_u64, to_f, to_f32, to_f64, to: @output

def to_b
@output == "true"
Expand Down
18 changes: 7 additions & 11 deletions src/compiler/crystal/codegen/call.cr
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,13 @@ class Crystal::CodeGenVisitor
end
node.args.each_with_index do |node_arg, i|
a_def_arg = a_def.args[i]
if autocast_literal?(node_arg)
if node_arg.supports_autocast?(@program.has_flag?("number_autocast"))
# If a call argument is a literal like 1 or :foo then
# it will match all the multidispatch overloads because
# it has a single type and there's no way some overload
# (from the ones we decided that match) won't match,
# because if it doesn't match then we wouldn't have included
# it in the match list.
# Matches, so nothing to do
else
result = and(result, match_type_id(node_arg.type, a_def_arg.type, arg_type_ids[i]))
Expand Down Expand Up @@ -427,16 +433,6 @@ class Crystal::CodeGenVisitor
@needs_value = old_needs_value
end

def autocast_literal?(call_arg)
# If a call argument is a literal like 1 or :foo then
# it will match all the multidispatch overloads because
# it has a single type and there's no way some overload
# (from the ones we decided that match) won't match,
# because if it doesn't match then we wouldn't have included
# it in the match list.
call_arg.is_a?(NumberLiteral) || call_arg.is_a?(SymbolLiteral)
end

def codegen_call(node, target_def, self_type, call_args)
body = target_def.body

Expand Down
54 changes: 53 additions & 1 deletion src/compiler/crystal/semantic/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,58 @@ module Crystal
false
end
end

# `number_autocast` defines if casting numeric expressions to larger ones is enabled
def supports_autocast?(number_autocast : Bool = false)
case self
when NumberLiteral, SymbolLiteral
true
else
if number_autocast
case self_type = self.type?
when IntegerType
case self_type.kind
when :i8, :u8, :i16, :u16, :i32, :u32, :i64, :u64
true
else
false
end
when FloatType
self_type.kind == :f32
end
else
false
end
end
end

def can_autocast_to?(other_type)
self_type = self.type

case {self_type, other_type}
when {IntegerType, IntegerType}
self_min, self_max = self_type.range
other_min, other_max = other_type.range
other_min <= self_min && self_max <= other_max
when {IntegerType, FloatType}
# Float32 mantissa has 23 bits,
# Float64 mantissa has 52 bits
case self_type.kind
when :i8, :u8, :i16, :u16
# Less than 23 bits, so convertable to Float32 and Float64 without precision loss
true
when :i32, :u32
# Less than 52 bits, so convertable to Float64 without precision loss
other_type.kind == :f64
else
false
end
when {FloatType, FloatType}
self_type.kind == :f32 && other_type.kind == :f64
else
false
end
end
end

class Var
Expand Down Expand Up @@ -807,7 +859,7 @@ module Crystal
end

class NumberLiteral
def can_be_autocast_to?(other_type)
def can_autocast_to?(other_type)
case {self.type, other_type}
when {IntegerType, IntegerType}
min, max = other_type.range
Expand Down
15 changes: 10 additions & 5 deletions src/compiler/crystal/semantic/bindings.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,22 @@ module Crystal
@type || @freeze_type
end

def type(*, with_literals = false)
def type(*, with_autocast = false)
type = self.type

if with_literals
if with_autocast
case self
when NumberLiteral
NumberLiteralType.new(type.program, self)
NumberAutocastType.new(type.program, self)
when SymbolLiteral
SymbolLiteralType.new(type.program, self)
SymbolAutocastType.new(type.program, self)
else
type
case type
when IntegerType, FloatType
NumberAutocastType.new(type.program, self)
else
type
end
end
else
type
Expand Down
Loading

0 comments on commit 5b18256

Please sign in to comment.