diff --git a/changelog.md b/changelog.md index ef51eee7af..c2211b510b 100644 --- a/changelog.md +++ b/changelog.md @@ -235,3 +235,6 @@ styledEcho "Red on Green.", resetStyle - ``\n`` is now only the single line feed character like in most other programming languages. The new platform specific newline escape sequence is written as ``\p``. This change only affects the Windows platform. +- Type inference for generic type parameters involving numeric types is now symetric. See + [Generic type inference for numeric types](https://nim-lang.org/docs/manual.html#generics-generic-type-inference-fornumeric-types) + for more information. diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index fffe92b2fa..5d53674601 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -913,6 +913,25 @@ proc isCovariantPtr(c: var TCandidate, f, a: PType): bool = else: return false +proc maxNumericType(prev, candidate: PType): PType = + let c = candidate.skipTypes({tyRange}) + template greater(s) = + if c.kind in s: result = c + case prev.kind + of tyInt: greater({tyInt64}) + of tyInt8: greater({tyInt, tyInt16, tyInt32, tyInt64}) + of tyInt16: greater({tyInt, tyInt32, tyInt64}) + of tyInt32: greater({tyInt64}) + + of tyUInt: greater({tyUInt64}) + of tyUInt8: greater({tyUInt, tyUInt16, tyUInt32, tyUInt64}) + of tyUInt16: greater({tyUInt, tyUInt32, tyUInt64}) + of tyUInt32: greater({tyUInt64}) + + of tyFloat32: greater({tyFloat64, tyFloat128}) + of tyFloat64: greater({tyFloat128}) + else: discard + proc typeRelImpl(c: var TCandidate, f, aOrig: PType, flags: TTypeRelFlags = {}): TTypeRelation = # typeRel can be used to establish various relationships between types: @@ -1602,9 +1621,16 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, elif x.kind == tyGenericParam: result = isGeneric else: - result = typeRel(c, x, a) # check if it fits - if result > isGeneric: result = isGeneric - + # Special type binding rule for numeric types. + # See section "Generic type inference for numeric types" of the + # manual for further details: + let rebinding = maxNumericType(x.skipTypes({tyRange}), a) + if rebinding != nil: + put(c, f, rebinding) + result = isGeneric + else: + result = typeRel(c, x, a) # check if it fits + if result > isGeneric: result = isGeneric of tyStatic: let prev = PType(idTableGet(c.bindings, f)) if prev == nil: diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt index 09fecbd959..80124bc94f 100644 --- a/doc/manual/generics.txt +++ b/doc/manual/generics.txt @@ -63,6 +63,8 @@ The following example shows a generic binary tree can be modelled: for str in preorder(root): stdout.writeLine(str) +The ``T`` is called a `generic type parameter `:idx:. + Is operator ----------- @@ -710,3 +712,30 @@ definition): But a ``bind`` is rarely useful because symbol binding from the definition scope is the default. + + +Generic type inference for numeric types +---------------------------------------- + +A `numeric`:idx: type is any signed, unsigned integer type, floating point +type or a subrange thereof. Let ``maxNumericType(T1, T2)`` be the "greater" +type of ``T1`` and ``T2``, that is the type that uses more bits. For +example ``maxNumericType(int32, int64) == int64``. ``maxNumericType`` is only +defined for numeric types of the same class (signed, unsigned, floating point). +``maxNumericType`` strips away subranges, +``maxNumericType(subrangeof(int16), int8)`` produces ``int16`` not its +subrange. The definition ``maxNumericType`` is extended to take a variable +number of arguments in the obvious way; +``maxNumericType(x, y, z) == maxNumericType(maxNumericType(x, y), z)``. + +A generic type parameter ``T`` that is bound to multiple numeric types ``N1``, +``N2``, ``N3``, ... during type checking is inferred to +be ``maxNumericType(N1, N2, N3, ...)``. This special type inference rule ensures +that the builtin arithmetic operators can be written in an intuitive way: + +.. code-block:: nim + proc `@`[T: int|int16|int32](x, y: T): T + + 4'i32 @ 6'i64 # inferred to be of type ``int64`` + + 4'i64 @ 6'i32 # inferred to be of type ``int64`` diff --git a/tests/generics/tspecial_numeric_inference.nim b/tests/generics/tspecial_numeric_inference.nim new file mode 100644 index 0000000000..d93544ba45 --- /dev/null +++ b/tests/generics/tspecial_numeric_inference.nim @@ -0,0 +1,12 @@ +discard """ + output: '''int64 +int64''' +""" + +import typetraits + +proc `@`[T: SomeInteger](x, y: T): T = x + +echo(type(5'i64 @ 6'i32)) + +echo(type(5'i32 @ 6'i64))