implements https://github.com/nim-lang/RFCs/issues/407 (#18793)
This commit is contained in:
parent
c2b20516d3
commit
cddf8ec6f6
9 changed files with 103 additions and 20 deletions
|
|
@ -160,6 +160,7 @@ type
|
||||||
importModuleMap*: Table[int, int] # (module.id, module.id)
|
importModuleMap*: Table[int, int] # (module.id, module.id)
|
||||||
lastTLineInfo*: TLineInfo
|
lastTLineInfo*: TLineInfo
|
||||||
sideEffects*: Table[int, seq[(TLineInfo, PSym)]] # symbol.id index
|
sideEffects*: Table[int, seq[(TLineInfo, PSym)]] # symbol.id index
|
||||||
|
inUncheckedAssignSection*: int
|
||||||
|
|
||||||
template config*(c: PContext): ConfigRef = c.graph.config
|
template config*(c: PContext): ConfigRef = c.graph.config
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -691,7 +691,11 @@ proc newHiddenAddrTaken(c: PContext, n: PNode): PNode =
|
||||||
else:
|
else:
|
||||||
result = newNodeIT(nkHiddenAddr, n.info, makeVarType(c, n.typ))
|
result = newNodeIT(nkHiddenAddr, n.info, makeVarType(c, n.typ))
|
||||||
result.add n
|
result.add n
|
||||||
if isAssignable(c, n) notin {arLValue, arLocalLValue}:
|
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))
|
localError(c.config, n.info, errVarForOutParamNeededX % renderNotLValue(n))
|
||||||
|
|
||||||
proc analyseIfAddressTaken(c: PContext, n: PNode): PNode =
|
proc analyseIfAddressTaken(c: PContext, n: PNode): PNode =
|
||||||
|
|
@ -738,8 +742,12 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) =
|
||||||
if i < t.len and t[i] != nil and
|
if i < t.len and t[i] != nil and
|
||||||
skipTypes(t[i], abstractInst-{tyTypeDesc}).kind in {tyVar}:
|
skipTypes(t[i], abstractInst-{tyTypeDesc}).kind in {tyVar}:
|
||||||
let it = n[i]
|
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:
|
if it.kind != nkHiddenAddr:
|
||||||
|
if aa == arDiscriminant and c.inUncheckedAssignSection > 0:
|
||||||
|
discard "allow access within a cast(unsafeAssign) section"
|
||||||
|
else:
|
||||||
localError(c.config, it.info, errVarForOutParamNeededX % $it)
|
localError(c.config, it.info, errVarForOutParamNeededX % $it)
|
||||||
# bug #5113: disallow newSeq(result) where result is a 'var T':
|
# bug #5113: disallow newSeq(result) where result is a 'var T':
|
||||||
if n[0].sym.magic in {mNew, mNewFinalize, mNewSeq}:
|
if n[0].sym.magic in {mNew, mNewFinalize, mNewSeq}:
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,7 @@ proc semConstructFields(c: PContext, n: PNode,
|
||||||
|
|
||||||
if selectedBranch != -1:
|
if selectedBranch != -1:
|
||||||
template badDiscriminatorError =
|
template badDiscriminatorError =
|
||||||
|
if c.inUncheckedAssignSection == 0:
|
||||||
let fields = fieldsPresentInBranch(selectedBranch)
|
let fields = fieldsPresentInBranch(selectedBranch)
|
||||||
localError(c.config, constrCtx.initExpr.info,
|
localError(c.config, constrCtx.initExpr.info,
|
||||||
("cannot prove that it's safe to initialize $1 with " &
|
("cannot prove that it's safe to initialize $1 with " &
|
||||||
|
|
@ -208,11 +209,12 @@ proc semConstructFields(c: PContext, n: PNode,
|
||||||
mergeInitStatus(result, initNone)
|
mergeInitStatus(result, initNone)
|
||||||
|
|
||||||
template wrongBranchError(i) =
|
template wrongBranchError(i) =
|
||||||
|
if c.inUncheckedAssignSection == 0:
|
||||||
let fields = fieldsPresentInBranch(i)
|
let fields = fieldsPresentInBranch(i)
|
||||||
localError(c.config, constrCtx.initExpr.info,
|
localError(c.config, constrCtx.initExpr.info,
|
||||||
"a case selecting discriminator '$1' with value '$2' " &
|
("a case selecting discriminator '$1' with value '$2' " &
|
||||||
"appears in the object construction, but the field(s) $3 " &
|
"appears in the object construction, but the field(s) $3 " &
|
||||||
"are in conflict with this value." %
|
"are in conflict with this value.") %
|
||||||
[discriminator.sym.name.s, discriminatorVal.renderTree, fields])
|
[discriminator.sym.name.s, discriminatorVal.renderTree, fields])
|
||||||
|
|
||||||
template valuesInConflictError(valsDiff) =
|
template valuesInConflictError(valsDiff) =
|
||||||
|
|
@ -251,6 +253,7 @@ proc semConstructFields(c: PContext, n: PNode,
|
||||||
badDiscriminatorError()
|
badDiscriminatorError()
|
||||||
elif discriminatorVal.sym.kind notin {skLet, skParam} or
|
elif discriminatorVal.sym.kind notin {skLet, skParam} or
|
||||||
discriminatorVal.sym.typ.kind in {tyVar}:
|
discriminatorVal.sym.typ.kind in {tyVar}:
|
||||||
|
if c.inUncheckedAssignSection == 0:
|
||||||
localError(c.config, discriminatorVal.info,
|
localError(c.config, discriminatorVal.info,
|
||||||
"runtime discriminator must be immutable if branch fields are " &
|
"runtime discriminator must be immutable if branch fields are " &
|
||||||
"initialized, a 'let' binding is required.")
|
"initialized, a 'let' binding is required.")
|
||||||
|
|
|
||||||
|
|
@ -961,6 +961,8 @@ proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
|
||||||
else:
|
else:
|
||||||
bc.exc = newNodeI(nkArgList, pragma.info)
|
bc.exc = newNodeI(nkArgList, pragma.info)
|
||||||
bc.exc.add n
|
bc.exc.add n
|
||||||
|
of wUncheckedAssign:
|
||||||
|
discard "handled in sempass1"
|
||||||
else:
|
else:
|
||||||
localError(tracked.config, pragma.info,
|
localError(tracked.config, pragma.info,
|
||||||
"invalid pragma block: " & $pragma)
|
"invalid pragma block: " & $pragma)
|
||||||
|
|
|
||||||
|
|
@ -2223,7 +2223,21 @@ proc semPragmaBlock(c: PContext, n: PNode): PNode =
|
||||||
checkSonsLen(n, 2, c.config)
|
checkSonsLen(n, 2, c.config)
|
||||||
let pragmaList = n[0]
|
let pragmaList = n[0]
|
||||||
pragma(c, nil, pragmaList, exprPragmas, isStatement = true)
|
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])
|
n[1] = semExpr(c, n[1])
|
||||||
|
dec c.inUncheckedAssignSection, inUncheckedAssignSection
|
||||||
result = n
|
result = n
|
||||||
result.typ = n[1].typ
|
result.typ = n[1].typ
|
||||||
for i in 0..<pragmaList.len:
|
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 c.graph.emptyNode
|
||||||
result.add arg
|
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,
|
proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType,
|
||||||
arg: PNode): PNode =
|
arg: PNode): PNode =
|
||||||
result = nil
|
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
|
let constraint = c.converters[i].typ.n[1].sym.constraint
|
||||||
if not constraint.isNil and not matchNodeKinds(constraint, arg):
|
if not constraint.isNil and not matchNodeKinds(constraint, arg):
|
||||||
continue
|
continue
|
||||||
if src.kind in {tyVar, tyLent} and not arg.isLValue:
|
if src.kind in {tyVar, tyLent} and not isLValue(c, arg):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
let destIsGeneric = containsGenericType(dest)
|
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}:
|
if argConverter.typ.kind notin {tyVar}:
|
||||||
m.firstMismatch.kind = kVarNeeded
|
m.firstMismatch.kind = kVarNeeded
|
||||||
noMatch()
|
noMatch()
|
||||||
elif not n.isLValue:
|
elif not isLValue(c, n):
|
||||||
m.firstMismatch.kind = kVarNeeded
|
m.firstMismatch.kind = kVarNeeded
|
||||||
noMatch()
|
noMatch()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ type
|
||||||
wMemTracker = "memtracker", wObjChecks = "objchecks",
|
wMemTracker = "memtracker", wObjChecks = "objchecks",
|
||||||
wIntDefine = "intdefine", wStrDefine = "strdefine", wBoolDefine = "booldefine",
|
wIntDefine = "intdefine", wStrDefine = "strdefine", wBoolDefine = "booldefine",
|
||||||
wCursor = "cursor", wNoalias = "noalias", wEffectsOf = "effectsOf",
|
wCursor = "cursor", wNoalias = "noalias", wEffectsOf = "effectsOf",
|
||||||
|
wUncheckedAssign = "uncheckedAssign",
|
||||||
|
|
||||||
wImmediate = "immediate", wConstructor = "constructor", wDestructor = "destructor",
|
wImmediate = "immediate", wConstructor = "constructor", wDestructor = "destructor",
|
||||||
wDelegator = "delegator", wOverride = "override", wImportCpp = "importcpp",
|
wDelegator = "delegator", wOverride = "override", wImportCpp = "importcpp",
|
||||||
|
|
|
||||||
|
|
@ -1854,6 +1854,41 @@ A small example:
|
||||||
let unknownKindBounded = range[nkAdd..nkSub](unknownKind)
|
let unknownKindBounded = range[nkAdd..nkSub](unknownKind)
|
||||||
z = Node(kind: unknownKindBounded, leftOp: Node(), rightOp: Node())
|
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
|
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")
|
t.curr = TokenObject(kind: Token.foo, foo: "foo")
|
||||||
|
|
||||||
echo "SUCCESS"
|
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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue