definite assignment analysis for let (#21024)
* draft for let daa * patch * fixes bugs * errors for global let variable reassignments * checkpoint * out param accepts let * add more tests * add documentation * merge tests
This commit is contained in:
parent
6d8cf25bd7
commit
b2c7019006
9 changed files with 144 additions and 30 deletions
|
|
@ -736,7 +736,7 @@ proc hasUnresolvedArgs(c: PContext, n: PNode): bool =
|
||||||
if hasUnresolvedArgs(c, n[i]): return true
|
if hasUnresolvedArgs(c, n[i]): return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
proc newHiddenAddrTaken(c: PContext, n: PNode): PNode =
|
proc newHiddenAddrTaken(c: PContext, n: PNode, isOutParam: bool): PNode =
|
||||||
if n.kind == nkHiddenDeref and not (c.config.backend == backendCpp or
|
if n.kind == nkHiddenDeref and not (c.config.backend == backendCpp or
|
||||||
sfCompileToCpp in c.module.flags):
|
sfCompileToCpp in c.module.flags):
|
||||||
checkSonsLen(n, 1, c.config)
|
checkSonsLen(n, 1, c.config)
|
||||||
|
|
@ -745,13 +745,17 @@ proc newHiddenAddrTaken(c: PContext, n: PNode): PNode =
|
||||||
result = newNodeIT(nkHiddenAddr, n.info, makeVarType(c, n.typ))
|
result = newNodeIT(nkHiddenAddr, n.info, makeVarType(c, n.typ))
|
||||||
result.add n
|
result.add n
|
||||||
let aa = isAssignable(c, n)
|
let aa = isAssignable(c, n)
|
||||||
|
let sym = getRoot(n)
|
||||||
if aa notin {arLValue, arLocalLValue}:
|
if aa notin {arLValue, arLocalLValue}:
|
||||||
if aa == arDiscriminant and c.inUncheckedAssignSection > 0:
|
if aa == arDiscriminant and c.inUncheckedAssignSection > 0:
|
||||||
discard "allow access within a cast(unsafeAssign) section"
|
discard "allow access within a cast(unsafeAssign) section"
|
||||||
|
elif strictDefs in c.features and aa == arAddressableConst and
|
||||||
|
sym != nil and sym.kind == skLet and isOutParam:
|
||||||
|
discard "allow let varaibles to be passed to out parameters"
|
||||||
else:
|
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, isOutParam: bool): PNode =
|
||||||
result = n
|
result = n
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkSym:
|
of nkSym:
|
||||||
|
|
@ -759,7 +763,7 @@ proc analyseIfAddressTaken(c: PContext, n: PNode): PNode =
|
||||||
if n.sym.typ != nil and
|
if n.sym.typ != nil and
|
||||||
skipTypes(n.sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}:
|
skipTypes(n.sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}:
|
||||||
incl(n.sym.flags, sfAddrTaken)
|
incl(n.sym.flags, sfAddrTaken)
|
||||||
result = newHiddenAddrTaken(c, n)
|
result = newHiddenAddrTaken(c, n, isOutParam)
|
||||||
of nkDotExpr:
|
of nkDotExpr:
|
||||||
checkSonsLen(n, 2, c.config)
|
checkSonsLen(n, 2, c.config)
|
||||||
if n[1].kind != nkSym:
|
if n[1].kind != nkSym:
|
||||||
|
|
@ -767,14 +771,14 @@ proc analyseIfAddressTaken(c: PContext, n: PNode): PNode =
|
||||||
return
|
return
|
||||||
if skipTypes(n[1].sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}:
|
if skipTypes(n[1].sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}:
|
||||||
incl(n[1].sym.flags, sfAddrTaken)
|
incl(n[1].sym.flags, sfAddrTaken)
|
||||||
result = newHiddenAddrTaken(c, n)
|
result = newHiddenAddrTaken(c, n, isOutParam)
|
||||||
of nkBracketExpr:
|
of nkBracketExpr:
|
||||||
checkMinSonsLen(n, 1, c.config)
|
checkMinSonsLen(n, 1, c.config)
|
||||||
if skipTypes(n[0].typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}:
|
if skipTypes(n[0].typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}:
|
||||||
if n[0].kind == nkSym: incl(n[0].sym.flags, sfAddrTaken)
|
if n[0].kind == nkSym: incl(n[0].sym.flags, sfAddrTaken)
|
||||||
result = newHiddenAddrTaken(c, n)
|
result = newHiddenAddrTaken(c, n, isOutParam)
|
||||||
else:
|
else:
|
||||||
result = newHiddenAddrTaken(c, n)
|
result = newHiddenAddrTaken(c, n, isOutParam)
|
||||||
|
|
||||||
proc analyseIfAddressTakenInCall(c: PContext, n: PNode) =
|
proc analyseIfAddressTakenInCall(c: PContext, n: PNode) =
|
||||||
checkMinSonsLen(n, 1, c.config)
|
checkMinSonsLen(n, 1, c.config)
|
||||||
|
|
@ -820,7 +824,7 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) =
|
||||||
if i < t.len and
|
if i < t.len and
|
||||||
skipTypes(t[i], abstractInst-{tyTypeDesc}).kind in {tyVar}:
|
skipTypes(t[i], abstractInst-{tyTypeDesc}).kind in {tyVar}:
|
||||||
if n[i].kind != nkHiddenAddr:
|
if n[i].kind != nkHiddenAddr:
|
||||||
n[i] = analyseIfAddressTaken(c, n[i])
|
n[i] = analyseIfAddressTaken(c, n[i], isOutParam(skipTypes(t[i], abstractInst-{tyTypeDesc})))
|
||||||
|
|
||||||
include semmagic
|
include semmagic
|
||||||
|
|
||||||
|
|
@ -1812,11 +1816,16 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
|
||||||
# a = b # both are vars, means: a[] = b[]
|
# a = b # both are vars, means: a[] = b[]
|
||||||
# a = b # b no 'var T' means: a = addr(b)
|
# a = b # b no 'var T' means: a = addr(b)
|
||||||
var le = a.typ
|
var le = a.typ
|
||||||
|
let assignable = isAssignable(c, a)
|
||||||
|
let root = getRoot(a)
|
||||||
|
let useStrictDefLet = root != nil and root.kind == skLet and
|
||||||
|
assignable == arAddressableConst and
|
||||||
|
strictDefs in c.features and isLocalSym(root)
|
||||||
if le == nil:
|
if le == nil:
|
||||||
localError(c.config, a.info, "expression has no type")
|
localError(c.config, a.info, "expression has no type")
|
||||||
elif (skipTypes(le, {tyGenericInst, tyAlias, tySink}).kind notin {tyVar} and
|
elif (skipTypes(le, {tyGenericInst, tyAlias, tySink}).kind notin {tyVar} and
|
||||||
isAssignable(c, a) in {arNone, arLentValue, arAddressableConst}) or (
|
assignable in {arNone, arLentValue, arAddressableConst} and not useStrictDefLet
|
||||||
skipTypes(le, abstractVar).kind in {tyOpenArray, tyVarargs} and views notin c.features):
|
) or (skipTypes(le, abstractVar).kind in {tyOpenArray, tyVarargs} and views notin c.features):
|
||||||
# Direct assignment to a discriminant is allowed!
|
# Direct assignment to a discriminant is allowed!
|
||||||
localError(c.config, a.info, errXCannotBeAssignedTo %
|
localError(c.config, a.info, errXCannotBeAssignedTo %
|
||||||
renderTree(a, {renderNoComments}))
|
renderTree(a, {renderNoComments}))
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,10 @@ type
|
||||||
escapingParams: IntSet
|
escapingParams: IntSet
|
||||||
PEffects = var TEffects
|
PEffects = var TEffects
|
||||||
|
|
||||||
|
const
|
||||||
|
errXCannotBeAssignedTo = "'$1' cannot be assigned to"
|
||||||
|
errLetNeedsInit = "'let' symbol requires an initialization"
|
||||||
|
|
||||||
proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo) =
|
proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo) =
|
||||||
if typ == nil: return
|
if typ == nil: return
|
||||||
when false:
|
when false:
|
||||||
|
|
@ -97,8 +101,8 @@ proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo) =
|
||||||
optSeqDestructors in tracked.config.globalOptions:
|
optSeqDestructors in tracked.config.globalOptions:
|
||||||
tracked.owner.flags.incl sfInjectDestructors
|
tracked.owner.flags.incl sfInjectDestructors
|
||||||
|
|
||||||
proc isLocalVar(a: PEffects, s: PSym): bool =
|
proc isLocalSym(a: PEffects, s: PSym): bool =
|
||||||
s.typ != nil and (s.kind in {skVar, skResult} or (s.kind == skParam and isOutParam(s.typ))) and
|
s.typ != nil and (s.kind in {skLet, skVar, skResult} or (s.kind == skParam and isOutParam(s.typ))) and
|
||||||
sfGlobal notin s.flags and s.owner == a.owner
|
sfGlobal notin s.flags and s.owner == a.owner
|
||||||
|
|
||||||
proc lockLocations(a: PEffects; pragma: PNode) =
|
proc lockLocations(a: PEffects; pragma: PNode) =
|
||||||
|
|
@ -173,10 +177,15 @@ proc initVar(a: PEffects, n: PNode; volatileCheck: bool) =
|
||||||
let n = skipHiddenDeref(n)
|
let n = skipHiddenDeref(n)
|
||||||
if n.kind != nkSym: return
|
if n.kind != nkSym: return
|
||||||
let s = n.sym
|
let s = n.sym
|
||||||
if isLocalVar(a, s):
|
if isLocalSym(a, s):
|
||||||
if volatileCheck: makeVolatile(a, s)
|
if volatileCheck: makeVolatile(a, s)
|
||||||
for x in a.init:
|
for x in a.init:
|
||||||
if x == s.id: return
|
if x == s.id:
|
||||||
|
if strictDefs in a.c.features and s.kind == skLet:
|
||||||
|
localError(a.config, n.info, errXCannotBeAssignedTo %
|
||||||
|
renderTree(n, {renderNoComments}
|
||||||
|
))
|
||||||
|
return
|
||||||
a.init.add s.id
|
a.init.add s.id
|
||||||
if a.scopes.getOrDefault(s.id) == a.currentBlock:
|
if a.scopes.getOrDefault(s.id) == a.currentBlock:
|
||||||
#[ Consider this case:
|
#[ Consider this case:
|
||||||
|
|
@ -204,7 +213,7 @@ proc initVarViaNew(a: PEffects, n: PNode) =
|
||||||
# 'x' is not nil, but that doesn't mean its "not nil" children
|
# 'x' is not nil, but that doesn't mean its "not nil" children
|
||||||
# are initialized:
|
# are initialized:
|
||||||
initVar(a, n, volatileCheck=true)
|
initVar(a, n, volatileCheck=true)
|
||||||
elif isLocalVar(a, s):
|
elif isLocalSym(a, s):
|
||||||
makeVolatile(a, s)
|
makeVolatile(a, s)
|
||||||
|
|
||||||
proc warnAboutGcUnsafe(n: PNode; conf: ConfigRef) =
|
proc warnAboutGcUnsafe(n: PNode; conf: ConfigRef) =
|
||||||
|
|
@ -322,7 +331,7 @@ proc useVar(a: PEffects, n: PNode) =
|
||||||
let s = n.sym
|
let s = n.sym
|
||||||
if a.inExceptOrFinallyStmt > 0:
|
if a.inExceptOrFinallyStmt > 0:
|
||||||
incl s.flags, sfUsedInFinallyOrExcept
|
incl s.flags, sfUsedInFinallyOrExcept
|
||||||
if isLocalVar(a, s):
|
if isLocalSym(a, s):
|
||||||
if sfNoInit in s.flags:
|
if sfNoInit in s.flags:
|
||||||
# If the variable is explicitly marked as .noinit. do not emit any error
|
# If the variable is explicitly marked as .noinit. do not emit any error
|
||||||
a.init.add s.id
|
a.init.add s.id
|
||||||
|
|
@ -331,6 +340,9 @@ proc useVar(a: PEffects, n: PNode) =
|
||||||
message(a.config, n.info, warnProveInit, s.name.s)
|
message(a.config, n.info, warnProveInit, s.name.s)
|
||||||
elif a.leftPartOfAsgn <= 0:
|
elif a.leftPartOfAsgn <= 0:
|
||||||
if strictDefs in a.c.features:
|
if strictDefs in a.c.features:
|
||||||
|
if s.kind == skLet:
|
||||||
|
localError(a.config, n.info, errLetNeedsInit)
|
||||||
|
else:
|
||||||
message(a.config, n.info, warnUninit, s.name.s)
|
message(a.config, n.info, warnUninit, s.name.s)
|
||||||
# prevent superfluous warnings about the same variable:
|
# prevent superfluous warnings about the same variable:
|
||||||
a.init.add s.id
|
a.init.add s.id
|
||||||
|
|
@ -637,7 +649,7 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, formals: PType; ar
|
||||||
let paramType = if formals != nil and argIndex < formals.len: formals[argIndex] else: nil
|
let paramType = if formals != nil and argIndex < formals.len: formals[argIndex] else: nil
|
||||||
if paramType != nil and paramType.kind in {tyVar}:
|
if paramType != nil and paramType.kind in {tyVar}:
|
||||||
invalidateFacts(tracked.guards, n)
|
invalidateFacts(tracked.guards, n)
|
||||||
if n.kind == nkSym and isLocalVar(tracked, n.sym):
|
if n.kind == nkSym and isLocalSym(tracked, n.sym):
|
||||||
makeVolatile(tracked, n.sym)
|
makeVolatile(tracked, n.sym)
|
||||||
if paramType != nil and paramType.kind == tyProc and tfGcSafe in paramType.flags:
|
if paramType != nil and paramType.kind == tyProc and tfGcSafe in paramType.flags:
|
||||||
let argtype = skipTypes(a.typ, abstractInst)
|
let argtype = skipTypes(a.typ, abstractInst)
|
||||||
|
|
@ -1025,7 +1037,7 @@ proc track(tracked: PEffects, n: PNode) =
|
||||||
# bug #15038: ensure consistency
|
# bug #15038: ensure consistency
|
||||||
if not hasDestructor(n.typ) and sameType(n.typ, n.sym.typ): n.typ = n.sym.typ
|
if not hasDestructor(n.typ) and sameType(n.typ, n.sym.typ): n.typ = n.sym.typ
|
||||||
of nkHiddenAddr, nkAddr:
|
of nkHiddenAddr, nkAddr:
|
||||||
if n[0].kind == nkSym and isLocalVar(tracked, n[0].sym):
|
if n[0].kind == nkSym and isLocalSym(tracked, n[0].sym):
|
||||||
useVarNoInitCheck(tracked, n[0], n[0].sym)
|
useVarNoInitCheck(tracked, n[0], n[0].sym)
|
||||||
else:
|
else:
|
||||||
track(tracked, n[0])
|
track(tracked, n[0])
|
||||||
|
|
@ -1065,7 +1077,7 @@ proc track(tracked: PEffects, n: PNode) =
|
||||||
when false: cstringCheck(tracked, n)
|
when false: cstringCheck(tracked, n)
|
||||||
if tracked.owner.kind != skMacro and n[0].typ.kind notin {tyOpenArray, tyVarargs}:
|
if tracked.owner.kind != skMacro and n[0].typ.kind notin {tyOpenArray, tyVarargs}:
|
||||||
createTypeBoundOps(tracked, n[0].typ, n.info)
|
createTypeBoundOps(tracked, n[0].typ, n.info)
|
||||||
if n[0].kind != nkSym or not isLocalVar(tracked, n[0].sym):
|
if n[0].kind != nkSym or not isLocalSym(tracked, n[0].sym):
|
||||||
checkForSink(tracked, n[1])
|
checkForSink(tracked, n[1])
|
||||||
if not tracked.hasDangerousAssign and n[0].kind != nkSym:
|
if not tracked.hasDangerousAssign and n[0].kind != nkSym:
|
||||||
tracked.hasDangerousAssign = true
|
tracked.hasDangerousAssign = true
|
||||||
|
|
|
||||||
|
|
@ -557,14 +557,15 @@ proc semVarMacroPragma(c: PContext, a: PNode, n: PNode): PNode =
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
template isLocalSym(sym: PSym): bool =
|
||||||
|
sym.kind in {skVar, skLet} and not
|
||||||
|
({sfGlobal, sfPure} * sym.flags != {} or
|
||||||
|
sfCompileTime in sym.flags) or
|
||||||
|
sym.kind in {skProc, skFunc, skIterator} and
|
||||||
|
sfGlobal notin sym.flags
|
||||||
|
|
||||||
template isLocalVarSym(n: PNode): bool =
|
template isLocalVarSym(n: PNode): bool =
|
||||||
n.kind == nkSym and
|
n.kind == nkSym and isLocalSym(n.sym)
|
||||||
(n.sym.kind in {skVar, skLet} and not
|
|
||||||
({sfGlobal, sfPure} * n.sym.flags != {} or
|
|
||||||
sfCompileTime in n.sym.flags) or
|
|
||||||
n.sym.kind in {skProc, skFunc, skIterator} and
|
|
||||||
sfGlobal notin n.sym.flags
|
|
||||||
)
|
|
||||||
|
|
||||||
proc usesLocalVar(n: PNode): bool =
|
proc usesLocalVar(n: PNode): bool =
|
||||||
for z in 1 ..< n.len:
|
for z in 1 ..< n.len:
|
||||||
|
|
@ -718,7 +719,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
|
||||||
else:
|
else:
|
||||||
checkNilable(c, v)
|
checkNilable(c, v)
|
||||||
# allow let to not be initialised if imported from C:
|
# allow let to not be initialised if imported from C:
|
||||||
if v.kind == skLet and sfImportc notin v.flags:
|
if v.kind == skLet and sfImportc notin v.flags and (strictDefs notin c.features or not isLocalSym(v)):
|
||||||
localError(c.config, a.info, errLetNeedsInit)
|
localError(c.config, a.info, errLetNeedsInit)
|
||||||
if sfCompileTime in v.flags:
|
if sfCompileTime in v.flags:
|
||||||
var x = newNodeI(result.kind, v.info)
|
var x = newNodeI(result.kind, v.info)
|
||||||
|
|
|
||||||
|
|
@ -1919,13 +1919,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.} =
|
proc isLValue(c: PContext; n: PNode, isOutParam = false): bool {.inline.} =
|
||||||
let aa = isAssignable(nil, n)
|
let aa = isAssignable(nil, n)
|
||||||
case aa
|
case aa
|
||||||
of arLValue, arLocalLValue, arStrange:
|
of arLValue, arLocalLValue, arStrange:
|
||||||
result = true
|
result = true
|
||||||
of arDiscriminant:
|
of arDiscriminant:
|
||||||
result = c.inUncheckedAssignSection > 0
|
result = c.inUncheckedAssignSection > 0
|
||||||
|
of arAddressableConst:
|
||||||
|
let sym = getRoot(n)
|
||||||
|
result = strictDefs in c.features and sym != nil and sym.kind == skLet and isOutParam
|
||||||
else:
|
else:
|
||||||
result = false
|
result = false
|
||||||
|
|
||||||
|
|
@ -2396,7 +2399,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 isLValue(c, n):
|
elif not (isLValue(c, n, isOutParam(formal.typ))):
|
||||||
m.firstMismatch.kind = kVarNeeded
|
m.firstMismatch.kind = kVarNeeded
|
||||||
noMatch()
|
noMatch()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1859,6 +1859,20 @@ before it is used. Thus the following is valid:
|
||||||
|
|
||||||
In this example every path does set `s` to a value before it is used.
|
In this example every path does set `s` to a value before it is used.
|
||||||
|
|
||||||
|
```nim
|
||||||
|
{.experimental: "strictDefs".}
|
||||||
|
|
||||||
|
proc test(cond: bool) =
|
||||||
|
let s: seq[string]
|
||||||
|
if cond:
|
||||||
|
s = @["y"]
|
||||||
|
else:
|
||||||
|
s = @[]
|
||||||
|
```
|
||||||
|
|
||||||
|
With `experimental: "strictDefs"`, `let` statements are allowed to not have an initial value, but every path should set `s` to a value before it is used.
|
||||||
|
|
||||||
|
|
||||||
`out` parameters
|
`out` parameters
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
||||||
34
tests/init/tlet.nim
Normal file
34
tests/init/tlet.nim
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{.experimental: "strictDefs".}
|
||||||
|
|
||||||
|
proc bar(x: out string) =
|
||||||
|
x = "abc"
|
||||||
|
|
||||||
|
proc foo() =
|
||||||
|
block:
|
||||||
|
let x: string
|
||||||
|
if true:
|
||||||
|
x = "abc"
|
||||||
|
else:
|
||||||
|
x = "def"
|
||||||
|
doAssert x == "abc"
|
||||||
|
block:
|
||||||
|
let y: string
|
||||||
|
bar(y)
|
||||||
|
doAssert y == "abc"
|
||||||
|
block:
|
||||||
|
let x: string
|
||||||
|
if true:
|
||||||
|
x = "abc"
|
||||||
|
discard "abc"
|
||||||
|
else:
|
||||||
|
x = "def"
|
||||||
|
discard "def"
|
||||||
|
doAssert x == "abc"
|
||||||
|
block: #
|
||||||
|
let x: int
|
||||||
|
block: #
|
||||||
|
let x: float
|
||||||
|
x = 1.234
|
||||||
|
doAssert x == 1.234
|
||||||
|
static: foo()
|
||||||
|
foo()
|
||||||
5
tests/init/tlet_uninit2.nim
Normal file
5
tests/init/tlet_uninit2.nim
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
discard """
|
||||||
|
errormsg: "'let' symbol requires an initialization"
|
||||||
|
"""
|
||||||
|
|
||||||
|
let x: int
|
||||||
24
tests/init/tlet_uninit3.nim
Normal file
24
tests/init/tlet_uninit3.nim
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
discard """
|
||||||
|
cmd: "nim check $file"
|
||||||
|
action: "reject"
|
||||||
|
nimout: '''
|
||||||
|
tlet_uninit3.nim(13, 5) Error: 'let' symbol requires an initialization
|
||||||
|
tlet_uninit3.nim(19, 5) Error: 'x' cannot be assigned to
|
||||||
|
tlet_uninit3.nim(23, 11) Error: 'let' symbol requires an initialization
|
||||||
|
'''
|
||||||
|
"""
|
||||||
|
|
||||||
|
{.experimental: "strictDefs".}
|
||||||
|
|
||||||
|
let global {.used.}: int
|
||||||
|
|
||||||
|
proc foo() =
|
||||||
|
block:
|
||||||
|
let x: int
|
||||||
|
x = 13
|
||||||
|
x = 14
|
||||||
|
|
||||||
|
block:
|
||||||
|
let x: int
|
||||||
|
doAssert x == 0
|
||||||
|
foo()
|
||||||
12
tests/init/tlet_uninit4.nim
Normal file
12
tests/init/tlet_uninit4.nim
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
discard """
|
||||||
|
errormsg: "type mismatch: got <string>"
|
||||||
|
"""
|
||||||
|
|
||||||
|
{.experimental: "strictDefs".}
|
||||||
|
|
||||||
|
proc foo(x: var string) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
proc bar() =
|
||||||
|
let x: string
|
||||||
|
foo(x)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue