better support for view types (#15436)
* you can put borrows into tables * enforces mutating views only mutate mutable data
This commit is contained in:
parent
9c86f4867e
commit
86d7b63e2a
5 changed files with 139 additions and 11 deletions
|
|
@ -142,8 +142,10 @@ proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc,
|
||||||
proc genBoundsCheck(p: BProc; arr, a, b: TLoc)
|
proc genBoundsCheck(p: BProc; arr, a, b: TLoc)
|
||||||
|
|
||||||
proc reifiedOpenArray(n: PNode): bool {.inline.} =
|
proc reifiedOpenArray(n: PNode): bool {.inline.} =
|
||||||
let x = trees.getRoot(n)
|
var x = n
|
||||||
if x != nil and x.kind == skParam:
|
while x.kind in {nkAddr, nkHiddenAddr, nkHiddenStdConv, nkHiddenDeref}:
|
||||||
|
x = x[0]
|
||||||
|
if x.kind == nkSym and x.sym.kind == skParam:
|
||||||
result = false
|
result = false
|
||||||
else:
|
else:
|
||||||
result = true
|
result = true
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
|
||||||
{tyChar, tyEnum, tyInt..tyFloat128, tyInt..tyUInt64}: result = t
|
{tyChar, tyEnum, tyInt..tyFloat128, tyInt..tyUInt64}: result = t
|
||||||
of tyOpenArray:
|
of tyOpenArray:
|
||||||
# you cannot nest openArrays/sinks/etc.
|
# you cannot nest openArrays/sinks/etc.
|
||||||
if (kind != skParam and views notin c.features) or taIsOpenArray in flags:
|
if (kind != skParam or taIsOpenArray in flags) and views notin c.features:
|
||||||
result = t
|
result = t
|
||||||
else:
|
else:
|
||||||
result = typeAllowedAux(marker, t[0], kind, c, flags+{taIsOpenArray})
|
result = typeAllowedAux(marker, t[0], kind, c, flags+{taIsOpenArray})
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,9 @@ type
|
||||||
VarFlag = enum
|
VarFlag = enum
|
||||||
ownsData,
|
ownsData,
|
||||||
preventCursor,
|
preventCursor,
|
||||||
isReassigned
|
isReassigned,
|
||||||
|
viewDoesMutate,
|
||||||
|
viewBorrowsFromConst
|
||||||
|
|
||||||
VarIndexKind = enum
|
VarIndexKind = enum
|
||||||
isEmptyRoot,
|
isEmptyRoot,
|
||||||
|
|
@ -478,9 +480,12 @@ proc pretendOwnsData(c: var Partitions, s: PSym) =
|
||||||
const
|
const
|
||||||
explainCursors = false
|
explainCursors = false
|
||||||
|
|
||||||
|
proc isConstSym(s: PSym): bool =
|
||||||
|
result = s.kind in {skConst, skLet} or isConstParam(s)
|
||||||
|
|
||||||
proc borrowFrom(c: var Partitions; dest: PSym; src: PNode) =
|
proc borrowFrom(c: var Partitions; dest: PSym; src: PNode) =
|
||||||
const
|
const
|
||||||
url = "; see https://nim-lang.github.io/Nim/manual_experimental.html#view-types-algorithm-path-expressions for details"
|
url = "see https://nim-lang.github.io/Nim/manual_experimental.html#view-types-algorithm-path-expressions for details"
|
||||||
|
|
||||||
let s = pathExpr(src, c.owner)
|
let s = pathExpr(src, c.owner)
|
||||||
if s == nil:
|
if s == nil:
|
||||||
|
|
@ -499,28 +504,50 @@ proc borrowFrom(c: var Partitions; dest: PSym; src: PNode) =
|
||||||
aliveStart: MinTime, aliveEnd: MaxTime)
|
aliveStart: MinTime, aliveEnd: MaxTime)
|
||||||
|
|
||||||
c.s[vid].borrowsFrom.add sourceIdx
|
c.s[vid].borrowsFrom.add sourceIdx
|
||||||
|
if isConstSym(s.sym):
|
||||||
|
c.s[vid].flags.incl viewBorrowsFromConst
|
||||||
else:
|
else:
|
||||||
discard "a valid borrow location that is a deeply constant expression so we have nothing to track"
|
let vid = variableId(c, dest)
|
||||||
|
if vid >= 0:
|
||||||
|
c.s[vid].flags.incl viewBorrowsFromConst
|
||||||
|
#discard "a valid borrow location that is a deeply constant expression so we have nothing to track"
|
||||||
|
|
||||||
|
|
||||||
proc borrowingCall(c: var Partitions; destType: PType; n: PNode; i: int) =
|
proc borrowingCall(c: var Partitions; destType: PType; n: PNode; i: int) =
|
||||||
let v = pathExpr(n[i], c.owner)
|
let v = pathExpr(n[i], c.owner)
|
||||||
if v.kind == nkSym:
|
if v != nil and v.kind == nkSym:
|
||||||
|
when false:
|
||||||
|
let isView = directViewType(destType) == immutableView
|
||||||
|
if n[0].kind == nkSym and n[0].sym.name.s == "[]=":
|
||||||
|
localError(c.config, n[i].info, "attempt to mutate an immutable view")
|
||||||
|
|
||||||
for j in i+1..<n.len:
|
for j in i+1..<n.len:
|
||||||
if getMagic(n[j]) == mSlice:
|
if getMagic(n[j]) == mSlice:
|
||||||
borrowFrom(c, v.sym, n[j])
|
borrowFrom(c, v.sym, n[j])
|
||||||
else:
|
else:
|
||||||
localError(c.config, n[i].info, "cannot determine the target of the borrow")
|
localError(c.config, n[i].info, "cannot determine the target of the borrow")
|
||||||
|
|
||||||
proc trackBorrow(c: var Partitions; dest, src: PNode) =
|
proc borrowingAsgn(c: var Partitions; dest, src: PNode) =
|
||||||
if dest.kind == nkSym:
|
if dest.kind == nkSym:
|
||||||
let vk = directViewType(dest.typ)
|
if directViewType(dest.typ) != noView:
|
||||||
if vk != noView:
|
|
||||||
borrowFrom(c, dest.sym, src)
|
borrowFrom(c, dest.sym, src)
|
||||||
|
elif dest.kind in {nkHiddenDeref, nkDerefExpr, nkBracketExpr}:
|
||||||
|
case directViewType(dest[0].typ)
|
||||||
|
of mutableView:
|
||||||
|
# we do not borrow, but we use the view to mutate the borrowed
|
||||||
|
# location:
|
||||||
|
let viewOrigin = pathExpr(dest, c.owner)
|
||||||
|
if viewOrigin.kind == nkSym:
|
||||||
|
let vid = variableId(c, viewOrigin.sym)
|
||||||
|
if vid >= 0:
|
||||||
|
c.s[vid].flags.incl viewDoesMutate
|
||||||
|
of immutableView:
|
||||||
|
localError(c.config, dest.info, "attempt to mutate a borrowed location from an immutable view")
|
||||||
|
of noView: discard "nothing to do"
|
||||||
|
|
||||||
proc deps(c: var Partitions; dest, src: PNode) =
|
proc deps(c: var Partitions; dest, src: PNode) =
|
||||||
if not c.performCursorInference:
|
if not c.performCursorInference:
|
||||||
trackBorrow(c, dest, src)
|
borrowingAsgn(c, dest, src)
|
||||||
|
|
||||||
var targets, sources: seq[PSym]
|
var targets, sources: seq[PSym]
|
||||||
allRoots(dest, targets)
|
allRoots(dest, targets)
|
||||||
|
|
@ -801,6 +828,7 @@ proc checkBorrowedLocations*(par: var Partitions; body: PNode; config: ConfigRef
|
||||||
if v.kind != skParam and classifyViewType(v.typ) != noView:
|
if v.kind != skParam and classifyViewType(v.typ) != noView:
|
||||||
let rid = root(par, i)
|
let rid = root(par, i)
|
||||||
if rid >= 0:
|
if rid >= 0:
|
||||||
|
var constViolation = false
|
||||||
for b in par.s[rid].borrowsFrom:
|
for b in par.s[rid].borrowsFrom:
|
||||||
let sid = root(par, b)
|
let sid = root(par, b)
|
||||||
if sid >= 0:
|
if sid >= 0:
|
||||||
|
|
@ -809,6 +837,16 @@ proc checkBorrowedLocations*(par: var Partitions; body: PNode; config: ConfigRef
|
||||||
if par.s[sid].sym.kind != skParam and par.s[sid].aliveEnd < par.s[rid].aliveEnd:
|
if par.s[sid].sym.kind != skParam and par.s[sid].aliveEnd < par.s[rid].aliveEnd:
|
||||||
localError(config, v.info, "'" & v.name.s & "' borrows from location '" & par.s[sid].sym.name.s &
|
localError(config, v.info, "'" & v.name.s & "' borrows from location '" & par.s[sid].sym.name.s &
|
||||||
"' which does not live long enough")
|
"' which does not live long enough")
|
||||||
|
if viewDoesMutate in par.s[rid].flags and isConstSym(par.s[sid].sym):
|
||||||
|
localError(config, v.info, "'" & v.name.s & "' borrows from the immutable location '" &
|
||||||
|
par.s[sid].sym.name.s & "' and attempts to mutate it")
|
||||||
|
constViolation = true
|
||||||
|
if {viewDoesMutate, viewBorrowsFromConst} * par.s[rid].flags == {viewDoesMutate, viewBorrowsFromConst} and
|
||||||
|
not constViolation:
|
||||||
|
# we do not track the constant expressions we allow to borrow from so
|
||||||
|
# we can only produce a more generic error message:
|
||||||
|
localError(config, v.info, "'" & v.name.s &
|
||||||
|
"' borrows from an immutable location and attempts to mutate it")
|
||||||
|
|
||||||
#if par.s[rid].con.kind == isRootOf and dangerousMutation(par.graphs[par.s[rid].con.graphIndex], par.s[i]):
|
#if par.s[rid].con.kind == isRootOf and dangerousMutation(par.graphs[par.s[rid].con.graphIndex], par.s[i]):
|
||||||
# cannotBorrow(config, s, par.graphs[par.s[rid].con.graphIndex])
|
# cannotBorrow(config, s, par.graphs[par.s[rid].con.graphIndex])
|
||||||
|
|
|
||||||
48
tests/views/tdont_mutate.nim
Normal file
48
tests/views/tdont_mutate.nim
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
discard """
|
||||||
|
cmd: "nim check --hints:off $file"
|
||||||
|
"""
|
||||||
|
|
||||||
|
import tables
|
||||||
|
|
||||||
|
{.experimental: "views".}
|
||||||
|
|
||||||
|
const
|
||||||
|
Whitespace = {' ', '\t', '\n', '\r'}
|
||||||
|
|
||||||
|
proc split*(s: string, seps: set[char] = Whitespace,
|
||||||
|
maxsplit: int = -1): Table[int, openArray[char]] =
|
||||||
|
var last = 0
|
||||||
|
var splits = maxsplit
|
||||||
|
result = initTable[int, openArray[char]]()
|
||||||
|
|
||||||
|
while last <= len(s):
|
||||||
|
var first = last
|
||||||
|
while last < len(s) and s[last] notin seps:
|
||||||
|
inc(last)
|
||||||
|
if splits == 0: last = len(s)
|
||||||
|
result[first] = toOpenArray(s, first, last-1)
|
||||||
|
|
||||||
|
result[first][0] = 'c' #[tt.Error
|
||||||
|
attempt to mutate a borrowed location from an immutable view
|
||||||
|
]#
|
||||||
|
|
||||||
|
if splits == 0: break
|
||||||
|
dec(splits)
|
||||||
|
inc(last)
|
||||||
|
|
||||||
|
proc `$`(x: openArray[char]): string =
|
||||||
|
result = newString(x.len)
|
||||||
|
for i in 0..<x.len: result[i] = x[i]
|
||||||
|
|
||||||
|
proc otherTest(x: int) =
|
||||||
|
var y: var int = x #[tt.Error
|
||||||
|
'y' borrows from the immutable location 'x' and attempts to mutate it
|
||||||
|
]#
|
||||||
|
y = 3
|
||||||
|
|
||||||
|
proc main() =
|
||||||
|
let words = split("asdf 231")
|
||||||
|
for i, x in words:
|
||||||
|
echo i, ": ", x
|
||||||
|
|
||||||
|
main()
|
||||||
40
tests/views/tsplit_into_table.nim
Normal file
40
tests/views/tsplit_into_table.nim
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
discard """
|
||||||
|
output: '''5: 231
|
||||||
|
0: asdf
|
||||||
|
'''
|
||||||
|
"""
|
||||||
|
|
||||||
|
import tables
|
||||||
|
|
||||||
|
{.experimental: "views".}
|
||||||
|
|
||||||
|
const
|
||||||
|
Whitespace = {' ', '\t', '\n', '\r'}
|
||||||
|
|
||||||
|
proc split*(s: string, seps: set[char] = Whitespace,
|
||||||
|
maxsplit: int = -1): Table[int, openArray[char]] =
|
||||||
|
var last = 0
|
||||||
|
var splits = maxsplit
|
||||||
|
result = initTable[int, openArray[char]]()
|
||||||
|
|
||||||
|
while last <= len(s):
|
||||||
|
var first = last
|
||||||
|
while last < len(s) and s[last] notin seps:
|
||||||
|
inc(last)
|
||||||
|
if splits == 0: last = len(s)
|
||||||
|
result[first] = toOpenArray(s, first, last-1)
|
||||||
|
|
||||||
|
if splits == 0: break
|
||||||
|
dec(splits)
|
||||||
|
inc(last)
|
||||||
|
|
||||||
|
proc `$`(x: openArray[char]): string =
|
||||||
|
result = newString(x.len)
|
||||||
|
for i in 0..<x.len: result[i] = x[i]
|
||||||
|
|
||||||
|
proc main() =
|
||||||
|
let words = split("asdf 231")
|
||||||
|
for i, x in words:
|
||||||
|
echo i, ": ", x
|
||||||
|
|
||||||
|
main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue