Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion compiler/sem.nim
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import
evaltempl, patterns, parampatterns, sempass2, linter, semmacrosanity,
lowerings, plugins/active, lineinfos, int128,
isolation_check, typeallowed, modulegraphs, enumtostr, concepts, astmsgs,
extccomp, layeredtable
extccomp, layeredtable, semderefs

import vtables
import std/[strtabs, math, tables, intsets, strutils, packedsets]
Expand Down Expand Up @@ -821,6 +821,7 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
else:
result = n
result = semStmt(c, result, {})
result = injectDerefs(c.config, result)
when false:
# Code generators are lazy now and can deal with undeclared procs, so these
# steps are not required anymore and actually harmful for the upcoming
Expand Down
273 changes: 273 additions & 0 deletions compiler/semderefs.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
import ast, lineinfos, msgs, options, renderer
import std/[strutils]

when defined(nimPreviewSlimSystem):
import std/assertions

type
Expects = enum
WantT
WantTButSkipDeref
WantVarT
WantVarTResult
WantMutableT # `var openArray` does need derefs but mutable locations regardless
WantForwarding

CurrentRoutine = object
returnExpects: Expects
firstParamKind: TTypeKind
firstParam: PSym
resultSym: PSym
routineSym: PSym

Context = object
config: ConfigRef
r: CurrentRoutine

proc tr(c: var Context; n: PNode; e: Expects): PNode

proc trSons(c: var Context; n: PNode; e: Expects): PNode =
for i in 0 ..< n.safeLen:
n[i] = tr(c, n[i], e)

result = n

proc validBorrowsFrom(c: var Context; n: PNode): bool =
# --------------------------------------------------------------------------
# We need to borrow from a location that is derived from the proc's
# first parameter.
# See RFC #7373
# --------------------------------------------------------------------------
result = false
var n = n
var someIndirection = false
while true:
case n.kind
of nkDotExpr, nkCheckedFieldExpr, nkBracketExpr, nkObjUpConv, nkObjDownConv:
n = n[0]
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
n = n[1]
of nkHiddenDeref, nkHiddenAddr, nkDerefExpr, nkAddr:
n = n[0]
someIndirection = true
of nkStmtList, nkStmtListExpr:
if n.len > 0 and n.typ != nil: n = n.lastSon
else: break
of nkCallKinds:
if n.len > 1:
let fnType = n[0].typ
n = n[1]
if fnType != nil and fnType.len > 1:
let firstParamType = fnType[1]
let mightForward = firstParamType.kind in {tyVar, tyLent}
if not mightForward:
someIndirection = true
else:
break
else:
break

if n.kind == nkSym:
if n.sym.kind == skParam and n.sym == c.r.firstParam:
result = c.r.firstParamKind in {tyVar, tyLent} or someIndirection
else:
# Allow borrow from global or captured variables for backwards compatibility.
result = n.sym.owner != c.r.routineSym
else:
result = false

proc skipToRoot(n: PNode): PNode =
var n = n
while true:
case n.kind
of nkDotExpr, nkCheckedFieldExpr, nkBracketExpr, nkObjUpConv, nkObjDownConv:
n = n[0]
of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkCast:
n = n[1]
of nkCallKinds:
if n.len > 1:
n = n[1]
else:
break
else:
break
result = n

proc borrowsFromReadonly(c: var Context; n: PNode; allowLet = false): bool =
let n = skipToRoot(n)
if n.kind == nkSym:
let tk = n.sym.typ.kind
case n.sym.kind:
of skConst:
result = true
of skLet:
if allowLet:
result = tk == tyLent
else:
result = tk notin {tyVar, tyLent}
of skVar:
result = tk == tyLent
of skParam:
result = tk notin {tyVar, tyLent, tySink}
else:
result = false
elif n.kind in nkLiterals + {nkBracket, nkObjConstr}:
result = true
else:
result = false

proc wantMutable(e: Expects): bool {.inline.} =
e in {WantVarT, WantVarTResult, WantMutableT}

proc trProcDecl(c: var Context; n: PNode): PNode =
if isGenericRoutine(n):
result = n
else:
var r = CurrentRoutine(returnExpects: WantT)
if n[namePos].kind == nkSym:
r.routineSym = n[namePos].sym
if n[namePos].sym.typ != nil:
let fnType = n[namePos].sym.typ
if fnType.len > 1:
r.firstParamKind = fnType[1].kind
if n[paramsPos].safeLen > 1 and n[paramsPos][1].kind == nkIdentDefs:
let firstParam = n[paramsPos][1]
if firstParam[0].kind == nkSym:
r.firstParam = firstParam[0].sym
if n.len > resultPos and n[resultPos].kind == nkSym:
r.resultSym = n[resultPos].sym
if n[resultPos].sym.typ != nil and n[resultPos].sym.typ.kind in {tyVar, tyLent}:
r.returnExpects = WantVarTResult

swap c.r, r
result = n
result[bodyPos] = tr(c, n[bodyPos], c.r.returnExpects)
swap c.r, r

proc trCallArgs(c: var Context; n: PNode; fnType: PType): PNode =
result = n
var p = 1
for i in 1 ..< n.len:
var e = WantT
if p >= fnType.len:
if tfVarargs in fnType.flags:
discard "call proc with varargs pragma"
else:
assert false
else:
let param = fnType[p]
let pk = param.kind
if pk in {tyVar, tyLent}:
e = WantVarT
if pk != tyVarargs:
# do not advance formal parameter when it is tyVarargs
inc p
result[i] = tr(c, n[i], e)

proc firstArgIsMutable(c: var Context; n: PNode): bool =
assert n.kind in nkCallKinds

if n.len > 1:
result = not borrowsFromReadonly(c, n[1])
else:
result = false

proc cannotPassToVar(c: var Context; info: TLineInfo; arg: PNode) =
localError(c.config, info, "cannot pass '$1' to var/out T parameter" % [renderTree(arg, {renderNoComments})])

proc trCall(c: var Context; n: PNode; e: Expects): PNode =
result = n
assert n.len > 0
let fnType = if n[0].kind == nkOpenSymChoice: n[0][0].typ else: n[0].typ
let retType = if fnType != nil and fnType.len > 0: fnType[0] else: nil
result[0] = tr(c, n[0], WantT)

if fnType == nil:
discard
elif retType != nil and retType.kind in {tyVar, tyLent}:
if e in {WantT, WantForwarding}:
result = trCallArgs(c, n, fnType)
elif e in {WantVarTResult, WantTButSkipDeref} or firstArgIsMutable(c, n):
result = trCallArgs(c, n, fnType)
else:
cannotPassToVar c, n.info, n
result = n
elif e.wantMutable:
cannotPassToVar c, n.info, n
else:
result = trCallArgs(c, n, fnType)

type
LvalueStatus = enum
Valid
InvalidBorrow
LocationIsConst

proc trAsgn(c: var Context; n: PNode): PNode =
var e = WantT
var err = Valid
if n[0].kind == nkSym and n[0].sym.kind == skResult and n[0].sym == c.r.resultSym:
e = c.r.returnExpects
if c.r.returnExpects == WantVarTResult:
if not validBorrowsFrom(c, n[1]):
err = InvalidBorrow
discard
case err:
of InvalidBorrow:
localError c.config, n.info, "cannot borrow from " & renderTree(n[1], {renderNoComments})
else:
result = tr(c, n[1], e)
result = n

proc trLocation(c: var Context; n: PNode; e: Expects): PNode =
let typ = n.typ
if typ != nil:
let k = typ.kind
if k in {tyVar, tyLent}:
if e.wantMutable:
# Consider `fvar(returnsVar(someLet))`: We must not allow this.
if borrowsFromReadonly(c, n):
cannotPassToVar c, n.info, n
result = n
else:
result = trSons(c, n, WantT)
else:
result = trSons(c, n, WantT)
elif e.wantMutable:
if e == WantVarTResult:
if n.kind == nkSym:
result = n
else:
result = trSons(c, n, WantT)
elif borrowsFromReadonly(c, n):
cannotPassToVar c, n.info, n
result = n
else:
result = trSons(c, n, WantT)
else:
result = trSons(c, n, WantT)
else:
result = n

proc tr(c: var Context; n: PNode; e: Expects): PNode =
case n.kind
of nkSym:
result = trLocation(c, n, e)
of nkLiterals:
if e.wantMutable:
cannotPassToVar c, n.info, n
result = n
of nkCallKinds:
result = trCall(c, n, e)
of nkDotExpr, nkCheckedFieldExpr, nkBracketExpr:
result = trLocation(c, n, e)
of nkAsgn:
result = trAsgn(c, n)
of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef, nkIteratorDef:
result = trProcDecl(c, n)
else:
result = trSons(c, n, e)

proc injectDerefs*(config: ConfigRef; n: PNode): PNode =
var c = Context(config: config, r: CurrentRoutine())
result = tr(c, n, WantT)
Loading