Skip to content

Commit

Permalink
allow conversions between var types of range types and base types (#…
Browse files Browse the repository at this point in the history
…24037)

refs #24032, split from #24036

Conversion from variables of range types or base types of range types to
the other are now considered mutable for `var` params, similar to how
distinct types are mutable when converted to their base type or vice
versa. There are 2 main differences:

1. Conversions from base types to range types need to emit
`nkChckRange`, which is not generated for things like tuple/object
fields.
2. Range types can still correspond to different types in the backend
when nested in other types, such as `set[range[3..5]]` vs
`set[range[0..5]]`.

Since the convertibility check for `var` params and a check whether to
emit a no-op for `nkConv` (and now also `nkChckRange`) so that the
output is still addressable both use `sameType`, we accomplish this by
adding a new flag to `sameType` that ignores range types, but only when
they're not nested in other types. The implementation for this might be
flawed, I didn't include children of some metatypes as "nested in other
types", but stuff like `tyGenericInst` params are respected.
  • Loading branch information
metagn authored Sep 3, 2024
1 parent 1ebdcb3 commit 538603e
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 22 deletions.
8 changes: 6 additions & 2 deletions compiler/ccgexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2234,12 +2234,16 @@ proc genRangeChck(p: BProc, n: PNode, d: var TLoc) =
raiseInstr(p, p.s(cpsStmts))
linefmt p, cpsStmts, "}$n", []

putIntoDest(p, d, n, "(($1) ($2))" %
if sameBackendTypeIgnoreRange(dest, n[0].typ):
# don't cast so an address can be taken for `var` conversions
putIntoDest(p, d, n, "($1)" % [rdCharLoc(a)], a.storage)
else:
putIntoDest(p, d, n, "(($1) ($2))" %
[getTypeDesc(p.module, dest), rdCharLoc(a)], a.storage)

proc genConv(p: BProc, e: PNode, d: var TLoc) =
let destType = e.typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink})
if sameBackendType(destType, e[1].typ):
if sameBackendTypeIgnoreRange(destType, e[1].typ):
expr(p, e[1], d)
else:
genSomeCast(p, e, d)
Expand Down
2 changes: 1 addition & 1 deletion compiler/parampatterns.nim
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ proc isAssignable*(owner: PSym, n: PNode): TAssignableResult =
if skipTypes(n.typ, abstractPtrs-{tyTypeDesc}).kind in
{tyOpenArray, tyTuple, tyObject}:
result = isAssignable(owner, n[1])
elif compareTypes(n.typ, n[1].typ, dcEqIgnoreDistinct):
elif compareTypes(n.typ, n[1].typ, dcEqIgnoreDistinct, {IgnoreRangeShallow}):
# types that are equal modulo distinction preserve l-value:
result = isAssignable(owner, n[1])
of nkHiddenDeref:
Expand Down
66 changes: 47 additions & 19 deletions compiler/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,7 @@ type
PickyCAliases # be picky about the distinction between 'cint' and 'int32'
IgnoreFlags # used for borrowed functions and methods; ignores the tfVarIsPtr flag
PickyBackendAliases # be picky about different aliases
IgnoreRangeShallow

TTypeCmpFlags* = set[TTypeCmpFlag]

Expand Down Expand Up @@ -1213,25 +1214,39 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
inc c.recCheck
else:
if containsOrIncl(c, a, b): return true
template maybeSkipRange(x: set[TTypeKind]): set[TTypeKind] =
if IgnoreRangeShallow in c.flags:
x + {tyRange}
else:
x

template withoutShallowFlags(body) =
let oldFlags = c.flags
c.flags.excl IgnoreRangeShallow
body
c.flags = oldFlags

if x == y: return true
var a = skipTypes(x, {tyAlias})
let aliasSkipSet = maybeSkipRange({tyAlias})
var a = skipTypes(x, aliasSkipSet)
while a.kind == tyUserTypeClass and tfResolved in a.flags:
a = skipTypes(a.last, {tyAlias})
var b = skipTypes(y, {tyAlias})
a = skipTypes(a.last, aliasSkipSet)
var b = skipTypes(y, aliasSkipSet)
while b.kind == tyUserTypeClass and tfResolved in b.flags:
b = skipTypes(b.last, {tyAlias})
b = skipTypes(b.last, aliasSkipSet)
assert(a != nil)
assert(b != nil)
if a.kind != b.kind:
case c.cmp
of dcEq: return false
of dcEqIgnoreDistinct:
a = a.skipTypes({tyDistinct, tyGenericInst})
b = b.skipTypes({tyDistinct, tyGenericInst})
let distinctSkipSet = maybeSkipRange({tyDistinct, tyGenericInst})
a = a.skipTypes(distinctSkipSet)
b = b.skipTypes(distinctSkipSet)
if a.kind != b.kind: return false
of dcEqOrDistinctOf:
a = a.skipTypes({tyDistinct, tyGenericInst})
let distinctSkipSet = maybeSkipRange({tyDistinct, tyGenericInst})
a = a.skipTypes(distinctSkipSet)
if a.kind != b.kind: return false

#[
Expand All @@ -1246,8 +1261,9 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
rhs = y.skipGenericAlias
if rhs.kind != tyGenericInst or lhs.base != rhs.base or rhs.kidsLen != lhs.kidsLen:
return false
for ff, aa in underspecifiedPairs(rhs, lhs, 1, -1):
if not sameTypeAux(ff, aa, c): return false
withoutShallowFlags:
for ff, aa in underspecifiedPairs(rhs, lhs, 1, -1):
if not sameTypeAux(ff, aa, c): return false
return true

case a.kind
Expand All @@ -1273,9 +1289,10 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
cycleCheck()
result = sameTypeAux(a.skipModifier, b.skipModifier, c)
of tyObject:
ifFastObjectTypeCheckFailed(a, b):
cycleCheck()
result = sameObjectStructures(a, b, c) and sameFlags(a, b)
withoutShallowFlags:
ifFastObjectTypeCheckFailed(a, b):
cycleCheck()
result = sameObjectStructures(a, b, c) and sameFlags(a, b)
of tyDistinct:
cycleCheck()
if c.cmp == dcEq:
Expand All @@ -1290,8 +1307,9 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
of tyError:
result = b.kind == tyError
of tyTuple:
cycleCheck()
result = sameTuple(a, b, c) and sameFlags(a, b)
withoutShallowFlags:
cycleCheck()
result = sameTuple(a, b, c) and sameFlags(a, b)
of tyTypeDesc:
if c.cmp == dcEqIgnoreDistinct: result = false
elif ExactTypeDescValues in c.flags:
Expand All @@ -1315,7 +1333,8 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
tyAnd, tyOr, tyNot, tyAnything, tyOwned:
cycleCheck()
if a.kind == tyUserTypeClass and a.n != nil: return a.n == b.n
result = sameChildrenAux(a, b, c)
withoutShallowFlags:
result = sameChildrenAux(a, b, c)
if result and IgnoreFlags notin c.flags:
if IgnoreTupleFields in c.flags:
result = a.flags * {tfVarIsPtr, tfIsOutParam} == b.flags * {tfVarIsPtr, tfIsOutParam}
Expand All @@ -1328,8 +1347,9 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
((ExactConstraints notin c.flags) or sameConstraints(a.n, b.n))
of tyRange:
cycleCheck()
result = sameTypeOrNilAux(a.elementType, b.elementType, c) and
sameValue(a.n[0], b.n[0]) and
result = sameTypeOrNilAux(a.elementType, b.elementType, c)
if result and IgnoreRangeShallow notin c.flags:
result = sameValue(a.n[0], b.n[0]) and
sameValue(a.n[1], b.n[1])
of tyAlias, tyInferred, tyIterable:
cycleCheck()
Expand All @@ -1339,8 +1359,9 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
# The type system must distinguish between `T[int] = object #[empty]#`
# and `T[float] = object #[empty]#`!
cycleCheck()
for ff, aa in underspecifiedPairs(a, b, 1, -1):
if not sameTypeAux(ff, aa, c): return false
withoutShallowFlags:
for ff, aa in underspecifiedPairs(a, b, 1, -1):
if not sameTypeAux(ff, aa, c): return false
result = sameTypeAux(a.skipModifier, b.skipModifier, c)
of tyNone: result = false
of tyConcept:
Expand All @@ -1352,6 +1373,13 @@ proc sameBackendType*(x, y: PType): bool =
c.cmp = dcEqIgnoreDistinct
result = sameTypeAux(x, y, c)

proc sameBackendTypeIgnoreRange*(x, y: PType): bool =
var c = initSameTypeClosure()
c.flags.incl IgnoreTupleFields
c.flags.incl IgnoreRangeShallow
c.cmp = dcEqIgnoreDistinct
result = sameTypeAux(x, y, c)

proc sameBackendTypePickyAliases*(x, y: PType): bool =
var c = initSameTypeClosure()
c.flags.incl {IgnoreTupleFields, PickyCAliases, PickyBackendAliases}
Expand Down
13 changes: 13 additions & 0 deletions tests/range/texplicitvarconv.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# related to issue #24032

proc `++`(n: var int) =
n += 1

type
r = range[ 0..15 ]

var a: r = 14

++int(a) # this should be mutable

doAssert a == 15
14 changes: 14 additions & 0 deletions tests/range/toutofrangevarconv.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
discard """
outputsub: "value out of range: 5 notin 0 .. 3 [RangeDefect]"
exitcode: "1"
"""

# make sure out of bounds range conversion is detected for `var` conversions

type R = range[0..3]

proc foo(x: var R) =
doAssert x in 0..3

var x = 5
foo(R(x))

0 comments on commit 538603e

Please sign in to comment.