diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 6f0d327d97..6c5b721c70 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -142,8 +142,10 @@ proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc, proc genBoundsCheck(p: BProc; arr, a, b: TLoc) proc reifiedOpenArray(n: PNode): bool {.inline.} = - let x = trees.getRoot(n) - if x != nil and x.kind == skParam: + var x = n + while x.kind in {nkAddr, nkHiddenAddr, nkHiddenStdConv, nkHiddenDeref}: + x = x[0] + if x.kind == nkSym and x.sym.kind == skParam: result = false else: result = true diff --git a/compiler/typeallowed.nim b/compiler/typeallowed.nim index aabaa46dd8..13eff35bef 100644 --- a/compiler/typeallowed.nim +++ b/compiler/typeallowed.nim @@ -121,7 +121,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, {tyChar, tyEnum, tyInt..tyFloat128, tyInt..tyUInt64}: result = t of tyOpenArray: # 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 else: result = typeAllowedAux(marker, t[0], kind, c, flags+{taIsOpenArray}) diff --git a/compiler/varpartitions.nim b/compiler/varpartitions.nim index 2047a90e1e..1fb4bd93ed 100644 --- a/compiler/varpartitions.nim +++ b/compiler/varpartitions.nim @@ -54,7 +54,9 @@ type VarFlag = enum ownsData, preventCursor, - isReassigned + isReassigned, + viewDoesMutate, + viewBorrowsFromConst VarIndexKind = enum isEmptyRoot, @@ -478,9 +480,12 @@ proc pretendOwnsData(c: var Partitions, s: PSym) = const 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) = 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) if s == nil: @@ -499,28 +504,50 @@ proc borrowFrom(c: var Partitions; dest: PSym; src: PNode) = aliveStart: MinTime, aliveEnd: MaxTime) c.s[vid].borrowsFrom.add sourceIdx + if isConstSym(s.sym): + c.s[vid].flags.incl viewBorrowsFromConst 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) = 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..= 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) = if not c.performCursorInference: - trackBorrow(c, dest, src) + borrowingAsgn(c, dest, src) var targets, sources: seq[PSym] 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: let rid = root(par, i) if rid >= 0: + var constViolation = false for b in par.s[rid].borrowsFrom: let sid = root(par, b) 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: localError(config, v.info, "'" & v.name.s & "' borrows from location '" & par.s[sid].sym.name.s & "' 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]): # cannotBorrow(config, s, par.graphs[par.s[rid].con.graphIndex]) diff --git a/tests/views/tdont_mutate.nim b/tests/views/tdont_mutate.nim new file mode 100644 index 0000000000..907aff018a --- /dev/null +++ b/tests/views/tdont_mutate.nim @@ -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..