diff --git a/spec/compiler/codegen/automatic_cast.cr b/spec/compiler/codegen/automatic_cast_spec.cr similarity index 95% rename from spec/compiler/codegen/automatic_cast.cr rename to spec/compiler/codegen/automatic_cast_spec.cr index 16dc08af8f08..def599b96587 100644 --- a/spec/compiler/codegen/automatic_cast.cr +++ b/spec/compiler/codegen/automatic_cast_spec.cr @@ -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 diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 411ab5d02e20..cd11eac784b7 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -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 diff --git a/spec/compiler/semantic/automatic_cast_spec.cr b/spec/compiler/semantic/automatic_cast_spec.cr index fc00247ac2f2..cca9762c8a8c 100644 --- a/spec/compiler/semantic/automatic_cast_spec.cr +++ b/spec/compiler/semantic/automatic_cast_spec.cr @@ -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 @@ -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 diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index dd70e46484f3..14067b3e508a 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -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 @@ -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" diff --git a/src/compiler/crystal/codegen/call.cr b/src/compiler/crystal/codegen/call.cr index 8cfbcd5f5591..d4bdc891cc1e 100644 --- a/src/compiler/crystal/codegen/call.cr +++ b/src/compiler/crystal/codegen/call.cr @@ -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])) @@ -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 diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index d2b8d8df5e00..5fdd23f581f7 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -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 @@ -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 diff --git a/src/compiler/crystal/semantic/bindings.cr b/src/compiler/crystal/semantic/bindings.cr index e745fe5109f4..536db24c75d5 100644 --- a/src/compiler/crystal/semantic/bindings.cr +++ b/src/compiler/crystal/semantic/bindings.cr @@ -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 diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index 2739eae08611..d4d50284cf0d 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -116,21 +116,21 @@ class Crystal::Call end def lookup_matches - lookup_matches(with_literals: false) + lookup_matches(with_autocast: false) rescue ex : RetryLookupWithLiterals - lookup_matches(with_literals: true) + lookup_matches(with_autocast: true) end - def lookup_matches(*, with_literals = false) + def lookup_matches(*, with_autocast = false) if args.any? { |arg| arg.is_a?(Splat) || arg.is_a?(DoubleSplat) } - lookup_matches_with_splat(with_literals) + lookup_matches_with_splat(with_autocast) else - arg_types = args.map(&.type(with_literals: with_literals)) - named_args_types = NamedArgumentType.from_args(named_args, with_literals) - matches = lookup_matches_without_splat arg_types, named_args_types, with_literals + arg_types = args.map(&.type(with_autocast: with_autocast)) + named_args_types = NamedArgumentType.from_args(named_args, with_autocast) + matches = lookup_matches_without_splat arg_types, named_args_types, with_autocast # If we checked for automatic casts, see if an ambiguous call was produced - if with_literals + if with_autocast arg_types.each &.check_restriction_exception named_args_types.try &.each &.type.check_restriction_exception end @@ -139,7 +139,7 @@ class Crystal::Call end end - def lookup_matches_with_splat(with_literals) + def lookup_matches_with_splat(with_autocast) # Check if all splat are of tuples arg_types = Array(Type).new(args.size * 2) named_args_types = nil @@ -170,7 +170,7 @@ class Crystal::Call arg.raise "argument to double splat must be a named tuple, not #{arg_type}" end else - arg_types << arg.type(with_literals: with_literals) + arg_types << arg.type(with_autocast: with_autocast) end end @@ -182,106 +182,106 @@ class Crystal::Call raise "duplicate key: #{named_arg.name}" if named_args_types.any? &.name.==(named_arg.name) named_args_types << NamedArgumentType.new( named_arg.name, - named_arg.value.type(with_literals: with_literals), + named_arg.value.type(with_autocast: with_autocast), ) end end - lookup_matches_without_splat arg_types, named_args_types, with_literals: with_literals + lookup_matches_without_splat arg_types, named_args_types, with_autocast: with_autocast end - def lookup_matches_without_splat(arg_types, named_args_types, with_literals) + def lookup_matches_without_splat(arg_types, named_args_types, with_autocast) if obj = @obj - lookup_matches_in(obj.type, arg_types, named_args_types, with_literals: with_literals) + lookup_matches_in(obj.type, arg_types, named_args_types, with_autocast: with_autocast) elsif name == "super" - lookup_super_matches(arg_types, named_args_types, with_literals: with_literals) + lookup_super_matches(arg_types, named_args_types, with_autocast: with_autocast) elsif name == "previous_def" - lookup_previous_def_matches(arg_types, named_args_types, with_literals: with_literals) + lookup_previous_def_matches(arg_types, named_args_types, with_autocast: with_autocast) elsif with_scope = @with_scope - lookup_matches_with_scope_in with_scope, arg_types, named_args_types, with_literals: with_literals + lookup_matches_with_scope_in with_scope, arg_types, named_args_types, with_autocast: with_autocast else - lookup_matches_in scope, arg_types, named_args_types, with_literals: with_literals + lookup_matches_in scope, arg_types, named_args_types, with_autocast: with_autocast end end - def lookup_matches_in(owner : AliasType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_literals = false) - lookup_matches_in(owner.remove_alias, arg_types, named_args_types, search_in_parents: search_in_parents, with_literals: with_literals) + def lookup_matches_in(owner : AliasType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) + lookup_matches_in(owner.remove_alias, arg_types, named_args_types, search_in_parents: search_in_parents, with_autocast: with_autocast) end - def lookup_matches_in(owner : UnionType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_literals = false) - owner.union_types.flat_map { |type| lookup_matches_in(type, arg_types, named_args_types, search_in_parents: search_in_parents, with_literals: with_literals) } + def lookup_matches_in(owner : UnionType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) + owner.union_types.flat_map { |type| lookup_matches_in(type, arg_types, named_args_types, search_in_parents: search_in_parents, with_autocast: with_autocast) } end - def lookup_matches_in(owner : Program, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_literals = false) - lookup_matches_in_type(owner, arg_types, named_args_types, self_type, def_name, search_in_parents: search_in_parents, with_literals: with_literals) + def lookup_matches_in(owner : Program, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) + lookup_matches_in_type(owner, arg_types, named_args_types, self_type, def_name, search_in_parents: search_in_parents, with_autocast: with_autocast) end - def lookup_matches_in(owner : FileModule, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_literals = false) - lookup_matches_in program, arg_types, named_args_types, search_in_parents: search_in_parents, with_literals: with_literals + def lookup_matches_in(owner : FileModule, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) + lookup_matches_in program, arg_types, named_args_types, search_in_parents: search_in_parents, with_autocast: with_autocast end - def lookup_matches_in(owner : NonGenericModuleType | GenericModuleInstanceType | GenericType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_literals = false) + def lookup_matches_in(owner : NonGenericModuleType | GenericModuleInstanceType | GenericType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) attach_subclass_observer owner including_types = owner.including_types if including_types - lookup_matches_in(including_types, arg_types, named_args_types, search_in_parents: search_in_parents, with_literals: with_literals) + lookup_matches_in(including_types, arg_types, named_args_types, search_in_parents: search_in_parents, with_autocast: with_autocast) else [] of Def end end - def lookup_matches_in(owner : LibType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_literals = false) + def lookup_matches_in(owner : LibType, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) raise "lib fun call is not supported in dispatch" end - def lookup_matches_in(owner : Type, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_literals = false) - lookup_matches_in_type(owner, arg_types, named_args_types, self_type, def_name, search_in_parents: search_in_parents, with_literals: with_literals) + def lookup_matches_in(owner : Type, arg_types, named_args_types, self_type = nil, def_name = self.name, search_in_parents = true, with_autocast = false) + lookup_matches_in_type(owner, arg_types, named_args_types, self_type, def_name, search_in_parents: search_in_parents, with_autocast: with_autocast) end - def lookup_matches_with_scope_in(owner, arg_types, named_args_types, with_literals = false) + def lookup_matches_with_scope_in(owner, arg_types, named_args_types, with_autocast = false) signature = CallSignature.new(name, arg_types, block, named_args_types) - matches = lookup_matches_checking_expansion(owner, signature, with_literals: with_literals) + matches = lookup_matches_checking_expansion(owner, signature, with_autocast: with_autocast) if matches.empty? && owner.class? && owner.abstract? - matches = owner.virtual_type.lookup_matches(signature, analyze_all: with_literals) + matches = owner.virtual_type.lookup_matches(signature, analyze_all: with_autocast) end if matches.empty? @uses_with_scope = false - return lookup_matches_in scope, arg_types, named_args_types, with_literals: with_literals + return lookup_matches_in scope, arg_types, named_args_types, with_autocast: with_autocast end @uses_with_scope = true - instantiate signature, matches, owner, self_type: nil, with_literals: with_literals + instantiate signature, matches, owner, self_type: nil, with_autocast: with_autocast end - def lookup_matches_in_type(owner, arg_types, named_args_types, self_type, def_name, search_in_parents, search_in_toplevel = true, with_literals = false) + def lookup_matches_in_type(owner, arg_types, named_args_types, self_type, def_name, search_in_parents, search_in_toplevel = true, with_autocast = false) signature = CallSignature.new(def_name, arg_types, block, named_args_types) matches = check_tuple_indexer(owner, def_name, args, arg_types) - matches ||= lookup_matches_checking_expansion(owner, signature, search_in_parents, with_literals: with_literals) + matches ||= lookup_matches_checking_expansion(owner, signature, search_in_parents, with_autocast: with_autocast) # If we didn't find a match and this call doesn't have a receiver, # and we are not at the top level, let's try searching the top-level if matches.empty? && !obj && owner != program && search_in_toplevel - program_matches = lookup_matches_with_signature(program, signature, search_in_parents, with_literals) + program_matches = lookup_matches_with_signature(program, signature, search_in_parents, with_autocast) matches = program_matches unless program_matches.empty? end if matches.empty? && owner.class? && owner.abstract? && !super? - matches = owner.virtual_type.lookup_matches(signature, analyze_all: with_literals) + matches = owner.virtual_type.lookup_matches(signature, analyze_all: with_autocast) end if matches.empty? defined_method_missing = owner.check_method_missing(signature, self) if defined_method_missing - matches = owner.lookup_matches(signature, analyze_all: with_literals) + matches = owner.lookup_matches(signature, analyze_all: with_autocast) elsif with_scope = @with_scope defined_method_missing = with_scope.check_method_missing(signature, self) if defined_method_missing - matches = with_scope.lookup_matches(signature, analyze_all: with_literals) + matches = with_scope.lookup_matches(signature, analyze_all: with_autocast) @uses_with_scope = true end end @@ -294,7 +294,7 @@ class Crystal::Call # compile errors, which will anyway appear once you add concrete # subclasses and instances. if def_name == "new" || !(!owner.metaclass? && owner.abstract_leaf?) - raise_matches_not_found(matches.owner || owner, def_name, arg_types, named_args_types, matches, with_literals: with_literals) + raise_matches_not_found(matches.owner || owner, def_name, arg_types, named_args_types, matches, with_autocast: with_autocast, number_autocast: program.has_flag?("number_autocast")) end end @@ -308,10 +308,10 @@ class Crystal::Call attach_subclass_observer instance_type.base_type end - instantiate signature, matches, owner, self_type, with_literals + instantiate signature, matches, owner, self_type, with_autocast end - def lookup_matches_checking_expansion(owner, signature, search_in_parents = true, with_literals = false) + def lookup_matches_checking_expansion(owner, signature, search_in_parents = true, with_autocast = false) # If this call is an expansion (because of default or named args) we must # resolve the call in the type that defined the original method, without # triggering a virtual lookup. But the context of lookup must be preserved. @@ -319,9 +319,9 @@ class Crystal::Call matches = bubbling_exception do target = parent_visitor.typed_def.original_owner if search_in_parents - target.lookup_matches(signature, analyze_all: with_literals) + target.lookup_matches(signature, analyze_all: with_autocast) else - target.lookup_matches_without_parents(signature, analyze_all: with_literals) + target.lookup_matches_without_parents(signature, analyze_all: with_autocast) end end matches.each do |match| @@ -330,37 +330,37 @@ class Crystal::Call end matches else - bubbling_exception { lookup_matches_with_signature(owner, signature, search_in_parents, with_literals) } + bubbling_exception { lookup_matches_with_signature(owner, signature, search_in_parents, with_autocast) } end end - def lookup_matches_with_signature(owner : Program, signature, search_in_parents, with_literals) + def lookup_matches_with_signature(owner : Program, signature, search_in_parents, with_autocast) location = self.location if location && (filename = location.original_filename) - matches = owner.lookup_private_matches(filename, signature, analyze_all: with_literals) + matches = owner.lookup_private_matches(filename, signature, analyze_all: with_autocast) end if matches if matches.empty? - matches = owner.lookup_matches(signature, analyze_all: with_literals) + matches = owner.lookup_matches(signature, analyze_all: with_autocast) end else - matches = owner.lookup_matches(signature, analyze_all: with_literals) + matches = owner.lookup_matches(signature, analyze_all: with_autocast) end matches end - def lookup_matches_with_signature(owner, signature, search_in_parents, with_literals) + def lookup_matches_with_signature(owner, signature, search_in_parents, with_autocast) if search_in_parents - owner.lookup_matches(signature, analyze_all: with_literals) + owner.lookup_matches(signature, analyze_all: with_autocast) else - owner.lookup_matches_without_parents(signature, analyze_all: with_literals) + owner.lookup_matches_without_parents(signature, analyze_all: with_autocast) end end - def instantiate(signature, matches, owner, self_type, with_literals) - matches.each &.remove_literals if with_literals + def instantiate(signature, matches, owner, self_type, with_autocast) + matches.each &.remove_literals if with_autocast block = @block @@ -631,7 +631,7 @@ class Crystal::Call end end - def lookup_super_matches(arg_types, named_args_types, with_literals) + def lookup_super_matches(arg_types, named_args_types, with_autocast) if scope.is_a?(Program) raise "there's no superclass in this scope" end @@ -669,16 +669,16 @@ class Crystal::Call if parents && parents.size > 0 parents.each_with_index do |parent, i| if parent.lookup_first_def(enclosing_def.name, block) - return lookup_matches_in_type(parent, arg_types, named_args_types, scope, enclosing_def.name, !in_initialize, search_in_toplevel: false, with_literals: with_literals) + return lookup_matches_in_type(parent, arg_types, named_args_types, scope, enclosing_def.name, !in_initialize, search_in_toplevel: false, with_autocast: with_autocast) end end - lookup_matches_in_type(parents.last, arg_types, named_args_types, scope, enclosing_def.name, !in_initialize, search_in_toplevel: false, with_literals: with_literals) + lookup_matches_in_type(parents.last, arg_types, named_args_types, scope, enclosing_def.name, !in_initialize, search_in_toplevel: false, with_autocast: with_autocast) else raise "there's no superclass in this scope" end end - def lookup_previous_def_matches(arg_types, named_args_types, with_literals) + def lookup_previous_def_matches(arg_types, named_args_types, with_autocast) enclosing_def = enclosing_def("previous_def") previous_item = enclosing_def.previous @@ -694,14 +694,14 @@ class Crystal::Call matches = Matches.new([match] of Match, true) unless signature.match(previous_item, context) - raise_matches_not_found scope, previous.name, arg_types, named_args_types, matches, with_literals: with_literals + raise_matches_not_found scope, previous.name, arg_types, named_args_types, matches, with_autocast: with_autocast, number_autocast: program.has_flag?("number_autocast") end unless scope.is_a?(Program) parent_visitor.check_self_closured end - typed_defs = instantiate signature, matches, scope, self_type: nil, with_literals: with_literals + typed_defs = instantiate signature, matches, scope, self_type: nil, with_autocast: with_autocast typed_defs.each do |typed_def| typed_def.next = parent_visitor.typed_def end diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index 0dff31532c01..61ca379ecb76 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -37,7 +37,7 @@ class Crystal::Path end class Crystal::Call - def raise_matches_not_found(owner, def_name, arg_types, named_args_types, matches = nil, with_literals = false) + def raise_matches_not_found(owner, def_name, arg_types, named_args_types, matches = nil, with_autocast = false, number_autocast = false) obj = @obj with_scope = @with_scope @@ -135,8 +135,8 @@ class Crystal::Call # If we made a lookup without the special rule for literals, # and we have literals in the call, try again with that special rule. - if with_literals == false && (args.any? { |arg| arg.is_a?(NumberLiteral) || arg.is_a?(SymbolLiteral) } || - named_args.try &.any? { |arg| arg.value.is_a?(NumberLiteral) || arg.value.is_a?(SymbolLiteral) }) + if with_autocast == false && (args.any?(&.supports_autocast? number_autocast) || + named_args.try &.any? &.value.supports_autocast? number_autocast) ::raise RetryLookupWithLiterals.new end diff --git a/src/compiler/crystal/semantic/class_vars_initializer_visitor.cr b/src/compiler/crystal/semantic/class_vars_initializer_visitor.cr index 525e81bb8f6a..b635a72f404f 100644 --- a/src/compiler/crystal/semantic/class_vars_initializer_visitor.cr +++ b/src/compiler/crystal/semantic/class_vars_initializer_visitor.cr @@ -69,8 +69,7 @@ module Crystal main_visitor.pushing_type(owner.as(ModuleType)) do # Check if we can autocast - if (node.is_a?(NumberLiteral) || node.is_a?(SymbolLiteral)) && - (class_var_type = class_var.type?) + if node.supports_autocast?(@program.has_flag?("number_autocast")) && (class_var_type = class_var.type?) cloned_node = node.clone cloned_node.accept MainVisitor.new(self) if casted_value = MainVisitor.check_automatic_cast(@program, cloned_node, class_var_type) diff --git a/src/compiler/crystal/semantic/cover.cr b/src/compiler/crystal/semantic/cover.cr index b2865decfca8..4674d0a12248 100644 --- a/src/compiler/crystal/semantic/cover.cr +++ b/src/compiler/crystal/semantic/cover.cr @@ -277,7 +277,7 @@ module Crystal delegate cover, cover_size, to: aliased_type end - class LiteralType + class AutocastType delegate cover, cover_size, to: (@match || literal.type) end end diff --git a/src/compiler/crystal/semantic/instance_vars_initializer_visitor.cr b/src/compiler/crystal/semantic/instance_vars_initializer_visitor.cr index b67c9f3f0d80..a93012345857 100644 --- a/src/compiler/crystal/semantic/instance_vars_initializer_visitor.cr +++ b/src/compiler/crystal/semantic/instance_vars_initializer_visitor.cr @@ -94,8 +94,7 @@ class Crystal::InstanceVarsInitializerVisitor < Crystal::SemanticVisitor next if scope.is_a?(GenericType) # Check if we can autocast - if (value.is_a?(NumberLiteral) || value.is_a?(SymbolLiteral)) && - (scope_initializer = scope_initializers[index]) + if value.supports_autocast?(@program.has_flag?("number_autocast")) && (scope_initializer = scope_initializers[index]) cloned_value = value.clone cloned_value.accept MainVisitor.new(program) if casted_value = MainVisitor.check_automatic_cast(@program, cloned_value, scope.lookup_instance_var(i.target.name).type) diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 2d2fbafadaab..f755880087b5 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -922,7 +922,7 @@ module Crystal def self.check_automatic_cast(program, value, var_type, assign = nil) if value.is_a?(NumberLiteral) && value.type != var_type - literal_type = NumberLiteralType.new(program, value) + literal_type = NumberAutocastType.new(program, value) restricted = literal_type.restrict(var_type, MatchContext.new(value.type, value.type)) if restricted.is_a?(IntegerType) || restricted.is_a?(FloatType) value.type = restricted @@ -931,7 +931,7 @@ module Crystal return value end elsif value.is_a?(SymbolLiteral) && value.type != var_type - literal_type = SymbolLiteralType.new(program, value) + literal_type = SymbolAutocastType.new(program, value) restricted = literal_type.restrict(var_type, MatchContext.new(value.type, value.type)) if restricted.is_a?(EnumType) member = restricted.find_member(value.value).not_nil! diff --git a/src/compiler/crystal/semantic/method_lookup.cr b/src/compiler/crystal/semantic/method_lookup.cr index 60eb553a27b9..53e626c069ca 100644 --- a/src/compiler/crystal/semantic/method_lookup.cr +++ b/src/compiler/crystal/semantic/method_lookup.cr @@ -8,13 +8,13 @@ require "../types" # overloads because the exact match will prevent them from being considered. # # If no matches are found we try again but this time with autocasting enabled. -# In `semantic/call.cr` this is when `with_literals` is `true`, and this is when +# In `semantic/call.cr` this is when `with_autocast` is `true`, and this is when # `analyze_all` will be `true` here. # # 2. Lookup is done with autocasting enabled. # # In this mode the types for NumberLiteral and SymbolLiteral are not the usual -# types but instead the special NumberLiteralType and SymbolLiteralType. +# types but instead the special NumberAutocastType and SymbolAutocastType. # # In this mode we also need to stop as soon as we find an exact match # (which just means when the first overload matches with autocasting, which @@ -52,9 +52,9 @@ require "../types" module Crystal record NamedArgumentType, name : String, type : Type do - def self.from_args(named_args : Array(NamedArgument)?, with_literals = false) + def self.from_args(named_args : Array(NamedArgument)?, with_autocast = false) named_args.try &.map do |named_arg| - new(named_arg.name, named_arg.value.type(with_literals: with_literals)) + new(named_arg.name, named_arg.value.type(with_autocast: with_autocast)) end end end @@ -366,7 +366,7 @@ module Crystal Match.new(a_def, (matched_arg_types || arg_types), context, matched_named_arg_types) end - def matches_exactly?(match : Match, *, with_literals : Bool = false) + def matches_exactly?(match : Match, *, with_autocast : Bool = false) arg_types_equal = self.arg_types.equals?(match.arg_types) do |x, y| x.compatible_with?(y) end diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 6bcf930c23b3..9a617ecdf165 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -1349,14 +1349,14 @@ module Crystal end end - class NumberLiteralType + class NumberAutocastType def restrict(other, context) if other.is_a?(IntegerType) || other.is_a?(FloatType) # Check for an exact match, which can't produce an ambiguous call if literal.type == other set_exact_match(other) other - elsif !exact_match? && literal.can_be_autocast_to?(other) + elsif !exact_match? && literal.can_autocast_to?(other) add_match(other) other else @@ -1373,11 +1373,11 @@ module Crystal end def compatible_with?(type) - literal.type == type || literal.can_be_autocast_to?(type) + literal.type == type || literal.can_autocast_to?(type) end end - class SymbolLiteralType + class SymbolAutocastType def restrict(other, context) case other when SymbolType diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index f0c7c8a19490..1e6213a31823 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -706,8 +706,8 @@ module Crystal end # Checks whether an exception needs to be raised because of a restriction - # failure. Only overwritten by literal types (NumberLiteralType and - # SymbolLiteralType) when they produce an ambiguous call. + # failure. Only overwritten by literal types (NumberAutocastType and + # SymbolAutocastType) when they produce an ambiguous call. def check_restriction_exception nil end @@ -1369,7 +1369,7 @@ module Crystal class VoidType < NamedType end - abstract class LiteralType < Type + abstract class AutocastType < Type # The most exact match type, or the first match otherwise getter match : Type? @@ -1404,7 +1404,14 @@ module Crystal def check_restriction_exception if all_matches = @all_matches - literal.raise "ambiguous call, implicit cast of #{literal} matches all of #{all_matches.join(", ")}" + literal_value = + case literal + when NumberLiteral, SymbolLiteral + literal.to_s + else + literal.type.to_s + end + literal.raise "ambiguous call, implicit cast of #{literal_value} matches all of #{all_matches.join(", ")}" end end end @@ -1412,9 +1419,9 @@ module Crystal # Type for a number literal: it has the specific type of the number literal # but can also match other types (like ints and floats) if the literal # fits in those types. - class NumberLiteralType < LiteralType + class NumberAutocastType < AutocastType # The literal associated with this type - getter literal : NumberLiteral + getter literal : ASTNode def initialize(program, @literal) super(program) @@ -1427,7 +1434,7 @@ module Crystal # Type for a symbol literal: it has the specific type of the symbol literal (SymbolType) # but can also match enums if their members match the symbol's name. - class SymbolLiteralType < LiteralType + class SymbolAutocastType < AutocastType # The literal associated with this type getter literal : SymbolLiteral @@ -1614,8 +1621,7 @@ module Crystal instance_var = instance.lookup_instance_var(initializer.name) # Check if automatic cast can be done - if instance_var.type != value.type && - (value.is_a?(NumberLiteral) || value.is_a?(SymbolLiteral)) + if instance_var.type != value.type && value.supports_autocast?(@program.has_flag?("number_autocast")) if casted_value = MainVisitor.check_automatic_cast(@program, value, instance_var.type) value = casted_value end