Skip to content

Commit

Permalink
implements nim-lang/RFCs#407 (#18793)
Browse files Browse the repository at this point in the history
  • Loading branch information
Araq authored Sep 3, 2021
1 parent c2b2051 commit cddf8ec
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 20 deletions.
1 change: 1 addition & 0 deletions compiler/semdata.nim
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ type
importModuleMap*: Table[int, int] # (module.id, module.id)
lastTLineInfo*: TLineInfo
sideEffects*: Table[int, seq[(TLineInfo, PSym)]] # symbol.id index
inUncheckedAssignSection*: int

template config*(c: PContext): ConfigRef = c.graph.config

Expand Down
16 changes: 12 additions & 4 deletions compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -691,8 +691,12 @@ proc newHiddenAddrTaken(c: PContext, n: PNode): PNode =
else:
result = newNodeIT(nkHiddenAddr, n.info, makeVarType(c, n.typ))
result.add n
if isAssignable(c, n) notin {arLValue, arLocalLValue}:
localError(c.config, n.info, errVarForOutParamNeededX % renderNotLValue(n))
let aa = isAssignable(c, n)
if aa notin {arLValue, arLocalLValue}:
if aa == arDiscriminant and c.inUncheckedAssignSection > 0:
discard "allow access within a cast(unsafeAssign) section"
else:
localError(c.config, n.info, errVarForOutParamNeededX % renderNotLValue(n))

proc analyseIfAddressTaken(c: PContext, n: PNode): PNode =
result = n
Expand Down Expand Up @@ -738,9 +742,13 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) =
if i < t.len and t[i] != nil and
skipTypes(t[i], abstractInst-{tyTypeDesc}).kind in {tyVar}:
let it = n[i]
if isAssignable(c, it) notin {arLValue, arLocalLValue}:
let aa = isAssignable(c, it)
if aa notin {arLValue, arLocalLValue}:
if it.kind != nkHiddenAddr:
localError(c.config, it.info, errVarForOutParamNeededX % $it)
if aa == arDiscriminant and c.inUncheckedAssignSection > 0:
discard "allow access within a cast(unsafeAssign) section"
else:
localError(c.config, it.info, errVarForOutParamNeededX % $it)
# bug #5113: disallow newSeq(result) where result is a 'var T':
if n[0].sym.magic in {mNew, mNewFinalize, mNewSeq}:
var arg = n[1] #.skipAddr
Expand Down
31 changes: 17 additions & 14 deletions compiler/semobjconstr.nim
Original file line number Diff line number Diff line change
Expand Up @@ -200,20 +200,22 @@ proc semConstructFields(c: PContext, n: PNode,

if selectedBranch != -1:
template badDiscriminatorError =
let fields = fieldsPresentInBranch(selectedBranch)
localError(c.config, constrCtx.initExpr.info,
("cannot prove that it's safe to initialize $1 with " &
"the runtime value for the discriminator '$2' ") %
[fields, discriminator.sym.name.s])
if c.inUncheckedAssignSection == 0:
let fields = fieldsPresentInBranch(selectedBranch)
localError(c.config, constrCtx.initExpr.info,
("cannot prove that it's safe to initialize $1 with " &
"the runtime value for the discriminator '$2' ") %
[fields, discriminator.sym.name.s])
mergeInitStatus(result, initNone)

template wrongBranchError(i) =
let fields = fieldsPresentInBranch(i)
localError(c.config, constrCtx.initExpr.info,
"a case selecting discriminator '$1' with value '$2' " &
"appears in the object construction, but the field(s) $3 " &
"are in conflict with this value." %
[discriminator.sym.name.s, discriminatorVal.renderTree, fields])
if c.inUncheckedAssignSection == 0:
let fields = fieldsPresentInBranch(i)
localError(c.config, constrCtx.initExpr.info,
("a case selecting discriminator '$1' with value '$2' " &
"appears in the object construction, but the field(s) $3 " &
"are in conflict with this value.") %
[discriminator.sym.name.s, discriminatorVal.renderTree, fields])

template valuesInConflictError(valsDiff) =
localError(c.config, discriminatorVal.info, ("possible values " &
Expand Down Expand Up @@ -251,9 +253,10 @@ proc semConstructFields(c: PContext, n: PNode,
badDiscriminatorError()
elif discriminatorVal.sym.kind notin {skLet, skParam} or
discriminatorVal.sym.typ.kind in {tyVar}:
localError(c.config, discriminatorVal.info,
"runtime discriminator must be immutable if branch fields are " &
"initialized, a 'let' binding is required.")
if c.inUncheckedAssignSection == 0:
localError(c.config, discriminatorVal.info,
"runtime discriminator must be immutable if branch fields are " &
"initialized, a 'let' binding is required.")
elif ctorCase[ctorIdx].kind == nkElifBranch:
localError(c.config, discriminatorVal.info, "branch initialization " &
"with a runtime discriminator is not supported inside of an " &
Expand Down
2 changes: 2 additions & 0 deletions compiler/sempass2.nim
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,8 @@ proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
else:
bc.exc = newNodeI(nkArgList, pragma.info)
bc.exc.add n
of wUncheckedAssign:
discard "handled in sempass1"
else:
localError(tracked.config, pragma.info,
"invalid pragma block: " & $pragma)
Expand Down
14 changes: 14 additions & 0 deletions compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2223,7 +2223,21 @@ proc semPragmaBlock(c: PContext, n: PNode): PNode =
checkSonsLen(n, 2, c.config)
let pragmaList = n[0]
pragma(c, nil, pragmaList, exprPragmas, isStatement = true)

var inUncheckedAssignSection = 0
for p in pragmaList:
if whichPragma(p) == wCast:
case whichPragma(p[1])
of wGcSafe, wNoSideEffect, wTags, wRaises:
discard "handled in sempass2"
of wUncheckedAssign:
inUncheckedAssignSection = 1
else:
localError(c.config, p.info, "invalid pragma block: " & $p)

inc c.inUncheckedAssignSection, inUncheckedAssignSection
n[1] = semExpr(c, n[1])
dec c.inUncheckedAssignSection, inUncheckedAssignSection
result = n
result.typ = n[1].typ
for i in 0..<pragmaList.len:
Expand Down
14 changes: 12 additions & 2 deletions compiler/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1879,6 +1879,16 @@ proc implicitConv(kind: TNodeKind, f: PType, arg: PNode, m: TCandidate,
result.add c.graph.emptyNode
result.add arg

proc isLValue(c: PContext; n: PNode): bool {.inline.} =
let aa = isAssignable(nil, n)
case aa
of arLValue, arLocalLValue, arStrange:
result = true
of arDiscriminant:
result = c.inUncheckedAssignSection > 0
else:
result = false

proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType,
arg: PNode): PNode =
result = nil
Expand All @@ -1895,7 +1905,7 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType,
let constraint = c.converters[i].typ.n[1].sym.constraint
if not constraint.isNil and not matchNodeKinds(constraint, arg):
continue
if src.kind in {tyVar, tyLent} and not arg.isLValue:
if src.kind in {tyVar, tyLent} and not isLValue(c, arg):
continue

let destIsGeneric = containsGenericType(dest)
Expand Down Expand Up @@ -2338,7 +2348,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int
if argConverter.typ.kind notin {tyVar}:
m.firstMismatch.kind = kVarNeeded
noMatch()
elif not n.isLValue:
elif not isLValue(c, n):
m.firstMismatch.kind = kVarNeeded
noMatch()

Expand Down
1 change: 1 addition & 0 deletions compiler/wordrecg.nim
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type
wMemTracker = "memtracker", wObjChecks = "objchecks",
wIntDefine = "intdefine", wStrDefine = "strdefine", wBoolDefine = "booldefine",
wCursor = "cursor", wNoalias = "noalias", wEffectsOf = "effectsOf",
wUncheckedAssign = "uncheckedAssign",

wImmediate = "immediate", wConstructor = "constructor", wDestructor = "destructor",
wDelegator = "delegator", wOverride = "override", wImportCpp = "importcpp",
Expand Down
35 changes: 35 additions & 0 deletions doc/manual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1854,6 +1854,41 @@ A small example:
let unknownKindBounded = range[nkAdd..nkSub](unknownKind)
z = Node(kind: unknownKindBounded, leftOp: Node(), rightOp: Node())
cast uncheckedAssign
--------------------

Some restrictions for case objects can be disabled via a `{.cast(unsafeAssign).}` section:

.. code-block:: nim
:test: "nim c $1"
type
TokenKind* = enum
strLit, intLit
Token = object
case kind*: TokenKind
of strLit:
s*: string
of intLit:
i*: int64
proc passToVar(x: var TokenKind) = discard
var t = Token(kind: strLit, s: "abc")
{.cast(uncheckedAssign).}:
# inside the 'cast' section it is allowed to pass 't.kind' to a 'var T' parameter:
passToVar(t.kind)
# inside the 'cast' section it is allowed to set field 's' even though the
# constructed 'kind' field has an unknown value:
t = Token(kind: t.kind, s: "abc")
# inside the 'cast' section it is allowed to assign to the 't.kind' field directly:
t.kind = intLit
Set type
--------

Expand Down
9 changes: 9 additions & 0 deletions tests/objvariant/treassign.nim
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,12 @@ t.curr = TokenObject(kind: Token.bar, bar: BasicNumber(value: 12.34))
t.curr = TokenObject(kind: Token.foo, foo: "foo")

echo "SUCCESS"

proc passToVar(x: var Token) = discard

{.cast(uncheckedAssign).}:
passToVar(t.curr.kind)

t.curr = TokenObject(kind: t.curr.kind, foo: "abc")

t.curr.kind = Token.foo

0 comments on commit cddf8ec

Please sign in to comment.