Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opt-in autocast for integers and floats #11431

Merged
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
309ad3d
Suppoort autocasting from smaller integer to large integer for vars
asterite Jul 3, 2020
683a3b3
Update spec/compiler/semantic/automatic_cast_spec.cr
asterite Jul 3, 2020
3538f36
Shorter way to write `supports_autocast?`
asterite Jul 3, 2020
524f2aa
with_literals -> with_autocast, LiteralType -> AutocastType
asterite Jul 3, 2020
6fd4812
Replace SymbolLiteralType with SymbolType in macro methods specs
asterite Jul 3, 2020
c4daab2
Support autocasting between integer and float type variables
asterite Jul 5, 2020
ec3f474
Rename automatic_cast.cr to automatic_cast_spec.cr
asterite Jul 5, 2020
1dd2ec0
Implement pending spec
asterite Jul 5, 2020
4d81b9a
Remove wrong spec
asterite Jul 22, 2020
ff038ff
Allow autocasting from Int64 to Int128
asterite Jul 22, 2020
438355e
Add a spec to prove that any expression can be autocast, not just vars
asterite Jul 22, 2020
fe3affe
merged master
beta-ziliani Nov 9, 2021
823cb8f
putting autocast of numeric types behind a flag
beta-ziliani Nov 9, 2021
471258b
turning useless spec into a useful one
beta-ziliani Nov 9, 2021
78034dc
changing the flag to a dynamic check with
beta-ziliani Nov 10, 2021
62ef072
Merge branch 'master' into beta/integer-autocast
beta-ziliani Nov 10, 2021
c36f2b9
guarding spec under flag
beta-ziliani Nov 10, 2021
910bc8e
fixing issues mentioned by @straight-shoota
beta-ziliani Nov 10, 2021
b2ac0ae
adding example suggested by @HertDevil
beta-ziliani Nov 11, 2021
e2156a0
renamed function as asked by @Sija, with name proposed by @straight-s…
beta-ziliani Nov 11, 2021
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
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
beta-ziliani marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -1523,15 +1523,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
107 changes: 106 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,88 @@ 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 "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 +678,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
52 changes: 52 additions & 0 deletions 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_be_autocast_to?(other_type)
straight-shoota marked this conversation as resolved.
Show resolved Hide resolved
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
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