mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-10 06:54:16 +00:00
implements https://github.com/nim-lang/RFCs/issues/407 (#18793)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 " &
|
||||
@@ -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 " &
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
--------
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user