Merge pull request #7736 from cooldome/range_float_type

Language feature: range float types
This commit is contained in:
Andreas Rumpf 2018-06-27 00:08:21 +02:00 • committed by GitHub
commit d8f7174dda
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 118 additions and 26 deletions

View file

@ -153,6 +153,8 @@
- Nim now supports `except` clause in the export statement. - Nim now supports `except` clause in the export statement.
- Range float types, example `range[0.0 .. Inf]`. More details in language manual.
### Tool changes ### Tool changes
- ``jsondoc2`` has been renamed ``jsondoc``, similar to how ``doc2`` was renamed - ``jsondoc2`` has been renamed ``jsondoc``, similar to how ``doc2`` was renamed

View file

@ -238,8 +238,8 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
if not hasUnknownTypes: if not hasUnknownTypes:
if not sameType(rangeT[0].skipTypes({tyRange}), rangeT[1].skipTypes({tyRange})): if not sameType(rangeT[0].skipTypes({tyRange}), rangeT[1].skipTypes({tyRange})):
localError(c.config, n.info, "type mismatch") localError(c.config, n.info, "type mismatch")
elif not rangeT[0].isOrdinalType: elif not rangeT[0].isOrdinalType and rangeT[0].kind notin tyFloat..tyFloat128:
localError(c.config, n.info, "ordinal type expected") localError(c.config, n.info, "ordinal or float type expected")
elif enumHasHoles(rangeT[0]): elif enumHasHoles(rangeT[0]):
localError(c.config, n.info, "enum '$1' has holes" % typeToString(rangeT[0])) localError(c.config, n.info, "enum '$1' has holes" % typeToString(rangeT[0]))

View file

@ -637,20 +637,22 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
else: discard else: discard
proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} = proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} =
let template checkRange[T](a0, a1, f0, f1: T): TTypeRelation =
a0 = firstOrd(nil, a)
a1 = lastOrd(nil, a)
f0 = firstOrd(nil, f)
f1 = lastOrd(nil, f)
if a0 == f0 and a1 == f1: if a0 == f0 and a1 == f1:
result = isEqual isEqual
elif a0 >= f0 and a1 <= f1: elif a0 >= f0 and a1 <= f1:
result = isConvertible isConvertible
elif a0 <= f1 and f0 <= a1: elif a0 <= f1 and f0 <= a1:
# X..Y and C..D overlap iff (X <= D and C <= Y) # X..Y and C..D overlap iff (X <= D and C <= Y)
result = isConvertible isConvertible
else: else:
result = isNone isNone
if f.isOrdinalType:
checkRange(firstOrd(nil, a), lastOrd(nil, a), firstOrd(nil, f), lastOrd(nil, f))
else:
checkRange(firstFloat(a), lastFloat(a), firstFloat(f), lastFloat(f))
proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
var var

View file

@ -89,13 +89,16 @@ proc isPureObject*(typ: PType): bool =
proc getOrdValue*(n: PNode): BiggestInt = proc getOrdValue*(n: PNode): BiggestInt =
case n.kind case n.kind
of nkCharLit..nkUInt64Lit: result = n.intVal of nkCharLit..nkUInt64Lit: n.intVal
of nkNilLit: result = 0 of nkNilLit: 0
of nkHiddenStdConv: result = getOrdValue(n.sons[1]) of nkHiddenStdConv: getOrdValue(n.sons[1])
else: else: high(BiggestInt)
#localError(n.info, errOrdinalTypeExpected)
# XXX check usages of getOrdValue proc getFloatValue*(n: PNode): BiggestFloat =
result = high(BiggestInt) case n.kind
of nkFloatLiterals: n.floatVal
of nkHiddenStdConv: getFloatValue(n.sons[1])
else: NaN
proc isIntLit*(t: PType): bool {.inline.} = proc isIntLit*(t: PType): bool {.inline.} =
result = t.kind == tyInt and t.n != nil and t.n.kind == nkIntLit result = t.kind == tyInt and t.n != nil and t.n.kind == nkIntLit
@ -593,6 +596,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
result = typeToStr[t.kind] result = typeToStr[t.kind]
result.addTypeFlags(t) result.addTypeFlags(t)
proc firstOrd*(conf: ConfigRef; t: PType): BiggestInt = proc firstOrd*(conf: ConfigRef; t: PType): BiggestInt =
case t.kind case t.kind
of tyBool, tyChar, tySequence, tyOpenArray, tyString, tyVarargs, tyProxy: of tyBool, tyChar, tySequence, tyOpenArray, tyString, tyVarargs, tyProxy:
@ -627,6 +631,21 @@ proc firstOrd*(conf: ConfigRef; t: PType): BiggestInt =
internalError(conf, "invalid kind for firstOrd(" & $t.kind & ')') internalError(conf, "invalid kind for firstOrd(" & $t.kind & ')')
result = 0 result = 0
proc firstFloat*(t: PType): BiggestFloat =
case t.kind
of tyFloat..tyFloat128: -Inf
of tyRange:
assert(t.n != nil) # range directly given:
assert(t.n.kind == nkRange)
getFloatValue(t.n.sons[0])
of tyVar: firstFloat(t.sons[0])
of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tyStatic, tyInferred:
firstFloat(lastSon(t))
else:
internalError(newPartialConfigRef(), "invalid kind for firstFloat(" & $t.kind & ')')
NaN
proc lastOrd*(conf: ConfigRef; t: PType; fixedUnsigned = false): BiggestInt = proc lastOrd*(conf: ConfigRef; t: PType; fixedUnsigned = false): BiggestInt =
case t.kind case t.kind
of tyBool: result = 1 of tyBool: result = 1
@ -667,6 +686,22 @@ proc lastOrd*(conf: ConfigRef; t: PType; fixedUnsigned = false): BiggestInt =
internalError(conf, "invalid kind for lastOrd(" & $t.kind & ')') internalError(conf, "invalid kind for lastOrd(" & $t.kind & ')')
result = 0 result = 0
proc lastFloat*(t: PType): BiggestFloat =
case t.kind
of tyFloat..tyFloat128: Inf
of tyVar: lastFloat(t.sons[0])
of tyRange:
assert(t.n != nil) # range directly given:
assert(t.n.kind == nkRange)
getFloatValue(t.n.sons[1])
of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tyStatic, tyInferred:
lastFloat(lastSon(t))
else:
internalError(newPartialConfigRef(), "invalid kind for lastFloat(" & $t.kind & ')')
NaN
proc lengthOrd*(conf: ConfigRef; t: PType): BiggestInt = proc lengthOrd*(conf: ConfigRef; t: PType): BiggestInt =
case t.kind case t.kind
of tyInt64, tyInt32, tyInt: result = lastOrd(conf, t) of tyInt64, tyInt32, tyInt: result = lastOrd(conf, t)

View file

@ -741,22 +741,26 @@ For further details, see `Convertible relation
Subrange types Subrange types
-------------- --------------
A subrange type is a range of values from an ordinal type (the base A subrange type is a range of values from an ordinal or floating point type (the base
type). To define a subrange type, one must specify it's limiting values: the type). To define a subrange type, one must specify it's limiting values: the
lowest and highest value of the type: lowest and highest value of the type:
.. code-block:: nim .. code-block:: nim
type type
Subrange = range[0..5] Subrange = range[0..5]
PositiveFloat = range[0.0..Inf]
``Subrange`` is a subrange of an integer which can only hold the values 0 ``Subrange`` is a subrange of an integer which can only hold the values 0
to 5. Assigning any other value to a variable of type ``Subrange`` is a to 5. ``PositiveFloat`` defines a subrange of all positive floating point values.
NaN does not belong to any subrange of floating point types.
Assigning any other value to a variable of type ``Subrange`` is a
checked runtime error (or static error if it can be statically checked runtime error (or static error if it can be statically
determined). Assignments from the base type to one of its subrange types determined). Assignments from the base type to one of its subrange types
(and vice versa) are allowed. (and vice versa) are allowed.
A subrange type has the same size as its base type (``int`` in the example). A subrange type has the same size as its base type (``int`` in the
Subrange example).
Pre-defined floating point types Pre-defined floating point types

View file

@ -0,0 +1,49 @@
discard """
cmd: "nim c -d:release --rangeChecks:on $file"
output: '''StrictPositiveRange
float
range fail expected
range fail expected
'''
"""
import math, fenv
type
Positive = range[0.0..Inf]
StrictPositive = range[minimumPositiveValue(float)..Inf]
Negative32 = range[-maximumPositiveValue(float32) .. -1.0'f32]
proc myoverload(x: float) =
echo "float"
proc myoverload(x: Positive) =
echo "PositiveRange"
proc myoverload(x: StrictPositive) =
echo "StrictPositiveRange"
let x = 9.0.StrictPositive
myoverload(x)
myoverload(9.0)
doAssert(sqrt(x) == 3.0)
var z = -10.0
try:
myoverload(StrictPositive(z))
except:
echo "range fail expected"
proc strictOnlyProc(x: StrictPositive): bool =
if x > 1.0: true else: false
let x2 = 5.0.Positive
doAssert(strictOnlyProc(x2))
try:
let x4 = 0.0.Positive
discard strictOnlyProc(x4)
except:
echo "range fail expected"