diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index cc6cf95f95a5..c6dff848eb80 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -49,34 +49,6 @@ module Crystal 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 @@ -859,22 +831,6 @@ module Crystal end end - class NumberLiteral - def can_autocast_to?(other_type) - case {self.type, other_type} - when {IntegerType, IntegerType} - min, max = other_type.range - min <= integer_value <= max - when {IntegerType, FloatType} - true - when {FloatType, FloatType} - true - else - false - end - end - end - # Fictitious node to mean a location in code shouldn't be reached. # This is used in the implicit `else` branch of a case. class Unreachable < ASTNode diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 36591d34740d..a4a95d69a97b 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -1346,68 +1346,72 @@ module Crystal end end - class NumberAutocastType + class AutocastType + # Returns true if the AST node associated with `self` denotes a value of the + # given *type*. + def matches_exactly?(type : Type) : Bool + false + end + + # Returns true if the AST node associated with `self` denotes a value that + # may be interpreted in the given *type*, but is itself not of that type. + def matches_partially?(type : Type) : Bool + false + end + 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 + if other.is_a?(Type) + if matches_exactly?(other) set_exact_match(other) - other - elsif !exact_match? && literal.can_autocast_to?(other) + return other + elsif !exact_match? && matches_partially?(other) add_match(other) - other - else - literal.type.restrict(other, context) + return other end - else - type = literal.type.restrict(other, context) || - super(other, context) - if type == self - type = @match || literal.type - end - type end + + literal_type = literal.type? + type = literal_type.try(&.restrict(other, context)) || super(other, context) + if type == self + # if *other* is an AST node (e.g. `Path`) or a complex type (e.g. + # `UnionType`), `@match` may be set from recursive calls to `#restrict`, + # so we propagate any exact matches found during those calls + type = @match || literal_type + end + type end def compatible_with?(type) - literal.type == type || literal.can_autocast_to?(type) + matches_exactly?(type) || matches_partially?(type) end end - class SymbolAutocastType - def restrict(other, context) - case other - when SymbolType - set_exact_match(other) - other - when EnumType - if !exact_match? && other.find_member(literal.value) - add_match(other) - other - else - literal.type.restrict(other, context) - end - else - type = literal.type.restrict(other, context) || - super(other, context) - if type == self - type = @match || literal.type - end - type - end + class NumberAutocastType + def matches_exactly?(type : IntegerType | FloatType) : Bool + literal.type == type end - def compatible_with?(type) - case type - when SymbolType - true - when EnumType - !!(type.find_member(literal.value)) + def matches_partially?(type : IntegerType | FloatType) : Bool + literal = self.literal + + if literal.is_a?(NumberLiteral) + literal.representable_in?(type) else - false + literal_type = literal.type + (literal_type.is_a?(IntegerType) || literal_type.is_a?(FloatType)) && literal_type.subset_of?(type) end end end + + class SymbolAutocastType + def matches_exactly?(type : SymbolType) : Bool + true + end + + def matches_partially?(type : EnumType) : Bool + !type.find_member(literal.value).nil? + end + end end private def get_generic_type(node, context) diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index c80f5f759ef9..49add5823f09 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -336,6 +336,25 @@ module Crystal end end + # Returns true if this literal is representable in the *other_type*. Used to + # define number literal autocasting. + # + # TODO: if *other_type* is a `FloatType` then precision loss and overflow + # may occur (#11710) + def representable_in?(other_type) + case {self.type, other_type} + when {IntegerType, IntegerType} + min, max = other_type.range + min <= integer_value <= max + when {IntegerType, FloatType} + true + when {FloatType, FloatType} + true + else + false + end + end + def clone_without_location NumberLiteral.new(@value, @kind) end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index ad8b65270945..20cd36d2f1f1 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -1336,6 +1336,31 @@ module Crystal raise "Bug: called 'range' for non-integer literal" end end + + # Returns true if every _finite_ member of this type is also exactly + # representable in the *other_type*. Used to define legal autocasts of + # number-typed variables + def subset_of?(other_type : IntegerType) : Bool + self_min, self_max = self.range + other_min, other_max = other_type.range + other_min <= self_min && self_max <= other_max + end + + # :ditto: + def subset_of?(other_type : FloatType) : Bool + # Float32 mantissa has 23 bits, + # Float64 mantissa has 52 bits + case 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 + end end class FloatType < PrimitiveType @@ -1359,6 +1384,18 @@ module Crystal raise "Bug: called 'range' for non-float literal" end end + + # Returns true if every _finite_ member of this type is also exactly + # representable in the *other_type*. Used to define legal autocasts of + # number-typed variables. + def subset_of?(other_type : IntegerType) : Bool + false + end + + # :ditto: + def subset_of?(other_type : FloatType) : Bool + kind.f32? && other_type.kind.f64? + end end class SymbolType < PrimitiveType