mirror of
https://github.com/nim-lang/Nim.git
synced 2026-02-19 09:28:33 +00:00
implemented large parts of the 'not nil' checking
This commit is contained in:
@@ -353,7 +353,7 @@ type
|
||||
nfSem # node has been checked for semantics
|
||||
|
||||
TNodeFlags* = set[TNodeFlag]
|
||||
TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 19)
|
||||
TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 23)
|
||||
tfVarargs, # procedure has C styled varargs
|
||||
tfNoSideEffect, # procedure type does not allow side effects
|
||||
tfFinal, # is the object final?
|
||||
@@ -380,7 +380,13 @@ type
|
||||
tfByRef, # pass object/tuple by reference (C backend)
|
||||
tfIterator, # type is really an iterator, not a tyProc
|
||||
tfShared, # type is 'shared'
|
||||
tfNotNil # type cannot be 'nil'
|
||||
tfNotNil, # type cannot be 'nil'
|
||||
|
||||
tfNeedsInit, # type constains a "not nil" constraint somewhere or some
|
||||
# other type so that it requires inititalization
|
||||
tfHasShared, # type constains a "shared" constraint modifier somewhere
|
||||
tfHasMeta, # type has "typedesc" or "expr" somewhere
|
||||
tfHasGCedMem, # type contains GC'ed memory
|
||||
|
||||
TTypeFlags* = set[TTypeFlag]
|
||||
|
||||
@@ -1168,14 +1174,25 @@ proc newSons(father: PNode, length: int) =
|
||||
else:
|
||||
setlen(father.sons, length)
|
||||
|
||||
proc addSon*(father, son: PType) {.deprecated.} =
|
||||
if isNil(father.sons): father.sons = @[]
|
||||
add(father.sons, son)
|
||||
#assert((father.kind != tyGenericInvokation) or (son.kind != tyGenericInst))
|
||||
proc propagateToOwner*(owner, elem: PType) =
|
||||
owner.flags = owner.flags + (elem.flags * {tfNeedsInit, tfHasShared,
|
||||
tfHasMeta, tfHasGCedMem})
|
||||
if tfNotNil in elem.flags:
|
||||
owner.flags.incl tfNeedsInit
|
||||
|
||||
if tfShared in elem.flags:
|
||||
owner.flags.incl tfHasShared
|
||||
|
||||
if elem.kind in {tyExpr, tyTypeDesc}:
|
||||
owner.flags.incl tfHasMeta
|
||||
elif elem.kind in {tyString, tyRef, tySequence} or
|
||||
elem.kind == tyProc and elem.callConv == ccClosure:
|
||||
owner.flags.incl tfHasGCedMem
|
||||
|
||||
proc rawAddSon*(father, son: PType) =
|
||||
if isNil(father.sons): father.sons = @[]
|
||||
add(father.sons, son)
|
||||
if not son.isNil: propagateToOwner(father, son)
|
||||
|
||||
proc addSon(father, son: PNode) =
|
||||
assert son != nil
|
||||
|
||||
@@ -43,6 +43,18 @@ proc getSysSym(name: string): PSym =
|
||||
result.typ = newType(tyError, systemModule)
|
||||
if result.kind == skStub: loadStub(result)
|
||||
|
||||
proc getSysMagic*(name: string, m: TMagic): PSym =
|
||||
var ti: TIdentIter
|
||||
let id = getIdent(name)
|
||||
result = InitIdentIter(ti, systemModule.tab, id)
|
||||
while result != nil:
|
||||
if result.kind == skStub: loadStub(result)
|
||||
if result.magic == m: return result
|
||||
result = NextIdentIter(ti, systemModule.tab)
|
||||
rawMessage(errSystemNeeds, name)
|
||||
result = newSym(skError, id, systemModule, systemModule.info)
|
||||
result.typ = newType(tyError, systemModule)
|
||||
|
||||
proc sysTypeFromName*(name: string): PType =
|
||||
result = getSysSym(name).typ
|
||||
|
||||
@@ -111,7 +123,9 @@ proc skipIntLit*(t: PType): PType {.inline.} =
|
||||
|
||||
proc AddSonSkipIntLit*(father, son: PType) =
|
||||
if isNil(father.sons): father.sons = @[]
|
||||
add(father.sons, son.skipIntLit)
|
||||
let s = son.skipIntLit
|
||||
add(father.sons, s)
|
||||
propagateToOwner(father, s)
|
||||
|
||||
proc setIntLitType*(result: PNode) =
|
||||
let i = result.intVal
|
||||
|
||||
@@ -106,7 +106,7 @@ type
|
||||
warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored,
|
||||
warnNilStatement, warnAnalysisLoophole,
|
||||
warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure,
|
||||
warnEachIdentIsTuple, warnShadowIdent, warnUninit, warnUser,
|
||||
warnEachIdentIsTuple, warnShadowIdent, warnProveInit, warnUninit, warnUser,
|
||||
hintSuccess, hintSuccessX,
|
||||
hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
|
||||
hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled,
|
||||
@@ -355,7 +355,8 @@ const
|
||||
warnImplicitClosure: "implicit closure convention: '$1' [ImplicitClosure]",
|
||||
warnEachIdentIsTuple: "each identifier is a tuple [EachIdentIsTuple]",
|
||||
warnShadowIdent: "shadowed identifier: '$1' [ShadowIdent]",
|
||||
warnUninit: "read from potentially uninitialized variable: '$1' [Uninit]",
|
||||
warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future. [ProveInit]",
|
||||
warnUninit: "'$1' might not have been initialized [Uninit]",
|
||||
warnUser: "$1 [User]",
|
||||
hintSuccess: "operation successful [Success]",
|
||||
hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#) [SuccessX]",
|
||||
@@ -375,14 +376,14 @@ const
|
||||
hintUser: "$1 [User]"]
|
||||
|
||||
const
|
||||
WarningsToStr*: array[0..20, string] = ["CannotOpenFile", "OctalEscape",
|
||||
WarningsToStr*: array[0..21, string] = ["CannotOpenFile", "OctalEscape",
|
||||
"XIsNeverRead", "XmightNotBeenInit",
|
||||
"Deprecated", "ConfigDeprecated",
|
||||
"SmallLshouldNotBeUsed", "UnknownMagic",
|
||||
"RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported",
|
||||
"CommentXIgnored", "NilStmt",
|
||||
"AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap",
|
||||
"ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", "Uninit",
|
||||
"ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", "ProveInit", "Uninit",
|
||||
"User"]
|
||||
|
||||
HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong",
|
||||
|
||||
@@ -50,6 +50,8 @@ proc add(code: var TPatternCode, op: TOpcode) {.inline.} =
|
||||
proc whichAlias*(p: PSym): TAliasRequest =
|
||||
if p.constraint != nil:
|
||||
result = TAliasRequest(p.constraint.strVal[0].ord)
|
||||
else:
|
||||
result = aqNone
|
||||
|
||||
proc compileConstraints(p: PNode, result: var TPatternCode) =
|
||||
case p.kind
|
||||
|
||||
@@ -50,7 +50,7 @@ const
|
||||
typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl,
|
||||
wPure, wHeader, wCompilerProc, wFinal, wSize, wExtern, wShallow,
|
||||
wImportcpp, wImportobjc, wError, wIncompleteStruct, wByCopy, wByRef,
|
||||
wInheritable, wGenSym, wInject}
|
||||
wInheritable, wGenSym, wInject, wRequiresInit}
|
||||
fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern,
|
||||
wImportcpp, wImportobjc, wError}
|
||||
varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl,
|
||||
@@ -253,11 +253,11 @@ proc processNote(c: PContext, n: PNode) =
|
||||
of wHint:
|
||||
var x = findStr(msgs.HintsToStr, n.sons[0].sons[1].ident.s)
|
||||
if x >= 0: nk = TNoteKind(x + ord(hintMin))
|
||||
else: invalidPragma(n)
|
||||
else: invalidPragma(n); return
|
||||
of wWarning:
|
||||
var x = findStr(msgs.WarningsToStr, n.sons[0].sons[1].ident.s)
|
||||
if x >= 0: nk = TNoteKind(x + ord(warnMin))
|
||||
else: InvalidPragma(n)
|
||||
else: InvalidPragma(n); return
|
||||
else:
|
||||
invalidPragma(n)
|
||||
return
|
||||
@@ -695,6 +695,10 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
|
||||
noVal(it)
|
||||
if sym.typ == nil: invalidPragma(it)
|
||||
else: incl(sym.typ.flags, tfIncompleteStruct)
|
||||
of wRequiresInit:
|
||||
noVal(it)
|
||||
if sym.typ == nil: invalidPragma(it)
|
||||
else: incl(sym.typ.flags, tfNeedsInit)
|
||||
of wByRef:
|
||||
noVal(it)
|
||||
if sym == nil or sym.typ == nil:
|
||||
|
||||
@@ -1601,6 +1601,25 @@ proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||
addSonSkipIntLit(typ, n.sons[i].typ)
|
||||
result.typ = typ
|
||||
|
||||
proc checkInitialized(n: PNode, ids: TIntSet, info: TLineInfo) =
|
||||
case n.kind
|
||||
of nkRecList:
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
checkInitialized(n.sons[i], ids, info)
|
||||
of nkRecCase:
|
||||
if (n.sons[0].kind != nkSym): InternalError(info, "checkInitialized")
|
||||
checkInitialized(n.sons[0], ids, info)
|
||||
when false:
|
||||
# XXX we cannot check here, as we don't know the branch!
|
||||
for i in countup(1, sonsLen(n) - 1):
|
||||
case n.sons[i].kind
|
||||
of nkOfBranch, nkElse: checkInitialized(lastSon(n.sons[i]), ids, info)
|
||||
else: internalError(info, "checkInitialized")
|
||||
of nkSym:
|
||||
if tfNeedsInit in n.sym.typ.flags and n.sym.name.id notin ids:
|
||||
Message(info, errGenerated, "field not initialized: " & n.sym.name.s)
|
||||
else: internalError(info, "checkInitialized")
|
||||
|
||||
proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||
var t = semTypeNode(c, n.sons[0], nil)
|
||||
result = n
|
||||
@@ -1611,6 +1630,7 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||
if t.kind != tyObject:
|
||||
localError(n.info, errGenerated, "object constructor needs an object type")
|
||||
return
|
||||
var objType = t
|
||||
var ids = initIntSet()
|
||||
for i in 1.. <n.len:
|
||||
let it = n.sons[i]
|
||||
@@ -1642,6 +1662,11 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||
localError(it.info, errUndeclaredFieldX, id.s)
|
||||
it.sons[1] = e
|
||||
# XXX object field name check for 'case objects' if the kind is static?
|
||||
if tfNeedsInit in objType.flags:
|
||||
while true:
|
||||
checkInitialized(objType.n, ids, n.info)
|
||||
if objType.sons[0] == nil: break
|
||||
objType = skipTypes(objType.sons[0], {tyGenericInst})
|
||||
|
||||
proc semBlock(c: PContext, n: PNode): PNode =
|
||||
result = n
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import
|
||||
intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
|
||||
wordrecg, strutils, options
|
||||
wordrecg, strutils, options, guards
|
||||
|
||||
# Second semantic checking pass over the AST. Necessary because the old
|
||||
# way had some inherent problems. Performs:
|
||||
@@ -75,7 +75,7 @@ type
|
||||
owner: PSym
|
||||
init: seq[int] # list of initialized variables
|
||||
# coming soon: "guard" tracking for 'let' variables
|
||||
|
||||
guards: TModel # nested guards
|
||||
PEffects = var TEffects
|
||||
|
||||
proc isLocalVar(a: PEffects, s: PSym): bool =
|
||||
@@ -93,11 +93,14 @@ proc useVar(a: PEffects, n: PNode) =
|
||||
let s = n.sym
|
||||
if isLocalVar(a, s):
|
||||
if s.id notin a.init:
|
||||
if true:
|
||||
Message(n.info, warnUninit, s.name.s)
|
||||
if tfNeedsInit in s.typ.flags:
|
||||
when true:
|
||||
Message(n.info, warnProveInit, s.name.s)
|
||||
else:
|
||||
Message(n.info, errGenerated,
|
||||
"'$1' might not have been initialized" % s.name.s)
|
||||
else:
|
||||
Message(n.info, errGenerated,
|
||||
"read from potentially uninitialized variable: '$1'" % s.name.s)
|
||||
Message(n.info, warnUninit, s.name.s)
|
||||
# prevent superfluous warnings about the same variable:
|
||||
a.init.add s.id
|
||||
|
||||
@@ -295,7 +298,7 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
|
||||
let tagSpec = effectSpec(pragma, wTags)
|
||||
mergeTags(tracked, tagSpec, n)
|
||||
|
||||
proc trackOperand(tracked: PEffects, n: PNode) =
|
||||
proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
|
||||
let op = n.typ
|
||||
if op != nil and op.kind == tyProc and n.kind != nkNilLit:
|
||||
InternalAssert op.n.sons[0].kind == nkEffectList
|
||||
@@ -312,16 +315,40 @@ proc trackOperand(tracked: PEffects, n: PNode) =
|
||||
else:
|
||||
mergeEffects(tracked, effectList.sons[exceptionEffects], n)
|
||||
mergeTags(tracked, effectList.sons[tagEffects], n)
|
||||
if paramType != nil and tfNotNil in paramType.flags and
|
||||
op != nil and tfNotNil notin op.flags:
|
||||
case impliesNotNil(tracked.guards, n)
|
||||
of impUnknown:
|
||||
Message(n.info, errGenerated,
|
||||
"cannot prove '$1' is not nil" % n.renderTree)
|
||||
of impNo:
|
||||
Message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree)
|
||||
of impYes: discard
|
||||
|
||||
proc breaksBlock(n: PNode): bool =
|
||||
case n.kind
|
||||
of nkStmtList, nkStmtListExpr:
|
||||
for c in n:
|
||||
if breaksBlock(c): return true
|
||||
of nkBreakStmt, nkReturnStmt, nkRaiseStmt:
|
||||
return true
|
||||
of nkCallKinds:
|
||||
if n.sons[0].kind == nkSym and sfNoReturn in n.sons[0].sym.flags:
|
||||
return true
|
||||
else:
|
||||
discard
|
||||
|
||||
proc trackCase(tracked: PEffects, n: PNode) =
|
||||
track(tracked, n.sons[0])
|
||||
let oldState = tracked.init.len
|
||||
var inter: TIntersection = @[]
|
||||
var toCover = 0
|
||||
for i in 1.. <n.len:
|
||||
let branch = n.sons[i]
|
||||
setLen(tracked.init, oldState)
|
||||
for i in 0 .. <branch.len:
|
||||
track(tracked, branch.sons[i])
|
||||
if not breaksBlock(branch.lastSon): inc toCover
|
||||
for i in oldState.. <tracked.init.len:
|
||||
addToIntersection(inter, tracked.init[i])
|
||||
let exh = case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind
|
||||
@@ -332,30 +359,41 @@ proc trackCase(tracked: PEffects, n: PNode) =
|
||||
setLen(tracked.init, oldState)
|
||||
if exh:
|
||||
for id, count in items(inter):
|
||||
if count == n.len-1: tracked.init.add id
|
||||
if count >= toCover: tracked.init.add id
|
||||
# else we can't merge
|
||||
|
||||
proc trackIf(tracked: PEffects, n: PNode) =
|
||||
track(tracked, n.sons[0].sons[0])
|
||||
let oldFacts = tracked.guards.len
|
||||
addFact(tracked.guards, n.sons[0].sons[0])
|
||||
let oldState = tracked.init.len
|
||||
|
||||
var inter: TIntersection = @[]
|
||||
var toCover = 0
|
||||
track(tracked, n.sons[0].sons[1])
|
||||
if not breaksBlock(n.sons[0].sons[1]): inc toCover
|
||||
for i in oldState.. <tracked.init.len:
|
||||
addToIntersection(inter, tracked.init[i])
|
||||
|
||||
for i in 1.. <n.len:
|
||||
let branch = n.sons[i]
|
||||
setLen(tracked.guards, oldFacts)
|
||||
for j in 0..i-1:
|
||||
addFactNeg(tracked.guards, n.sons[j].sons[0])
|
||||
if branch.len > 1:
|
||||
addFact(tracked.guards, branch.sons[0])
|
||||
setLen(tracked.init, oldState)
|
||||
for i in 0 .. <branch.len:
|
||||
track(tracked, branch.sons[i])
|
||||
if not breaksBlock(branch.lastSon): inc toCover
|
||||
for i in oldState.. <tracked.init.len:
|
||||
addToIntersection(inter, tracked.init[i])
|
||||
setLen(tracked.init, oldState)
|
||||
if lastSon(n).len == 1:
|
||||
for id, count in items(inter):
|
||||
if count == n.len: tracked.init.add id
|
||||
if count >= toCover: tracked.init.add id
|
||||
# else we can't merge as it is not exhaustive
|
||||
setLen(tracked.guards, oldFacts)
|
||||
|
||||
proc trackBlock(tracked: PEffects, n: PNode) =
|
||||
if n.kind in {nkStmtList, nkStmtListExpr}:
|
||||
@@ -377,6 +415,9 @@ proc isTrue(n: PNode): bool =
|
||||
n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or
|
||||
n.kind == nkIntLit and n.intVal != 0
|
||||
|
||||
proc paramType(op: PType, i: int): PType =
|
||||
if op != nil and i < op.len: result = op.sons[i]
|
||||
|
||||
proc track(tracked: PEffects, n: PNode) =
|
||||
case n.kind
|
||||
of nkSym:
|
||||
@@ -404,11 +445,12 @@ proc track(tracked: PEffects, n: PNode) =
|
||||
else:
|
||||
mergeEffects(tracked, effectList.sons[exceptionEffects], n)
|
||||
mergeTags(tracked, effectList.sons[tagEffects], n)
|
||||
for i in 1 .. <len(n): trackOperand(tracked, n.sons[i])
|
||||
for i in 1 .. <len(n): trackOperand(tracked, n.sons[i], paramType(op, i))
|
||||
if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize,
|
||||
mNewSeq, mShallowCopy}:
|
||||
# may not look like an assignment, but it is:
|
||||
initVar(tracked, n.sons[1])
|
||||
# XXX new(objWithNotNil) is not initialized properly!
|
||||
for i in 0 .. <safeLen(n):
|
||||
track(tracked, n.sons[i])
|
||||
of nkTryStmt: trackTryStmt(tracked, n)
|
||||
@@ -510,8 +552,14 @@ proc trackProc*(s: PSym, body: PNode) =
|
||||
t.tags = effects.sons[tagEffects]
|
||||
t.owner = s
|
||||
t.init = @[]
|
||||
t.guards = @[]
|
||||
track(t, body)
|
||||
|
||||
if not isEmptyType(s.typ.sons[0]) and tfNeedsInit in s.typ.sons[0].flags and
|
||||
s.kind in {skProc, skConverter, skMethod}:
|
||||
var res = s.ast.sons[resultPos].sym # get result symbol
|
||||
if res.id notin t.init:
|
||||
Message(body.info, warnProveInit, "result")
|
||||
let p = s.ast.sons[pragmasPos]
|
||||
let raisesSpec = effectSpec(p, wRaises)
|
||||
if not isNil(raisesSpec):
|
||||
|
||||
@@ -374,7 +374,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
|
||||
if warnShadowIdent in gNotes and not identWithin(def, v.name):
|
||||
Message(a.info, warnShadowIdent, v.name.s)
|
||||
if def != nil and def.kind != nkEmpty:
|
||||
# this is only needed for the evaluation pass:
|
||||
# this is needed for the evaluation pass and for the guard checking:
|
||||
v.ast = def
|
||||
if sfThread in v.flags: LocalError(def.info, errThreadvarCannotInit)
|
||||
if a.kind != nkVarTuple:
|
||||
|
||||
@@ -23,7 +23,7 @@ proc newConstraint(c: PContext, k: TTypeKind): PType =
|
||||
|
||||
proc semEnum(c: PContext, n: PNode, prev: PType): PType =
|
||||
if n.sonsLen == 0: return newConstraint(c, tyEnum)
|
||||
var
|
||||
var
|
||||
counter, x: BiggestInt
|
||||
e: PSym
|
||||
base: PType
|
||||
@@ -39,6 +39,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
|
||||
counter = lastOrd(base) + 1
|
||||
rawAddSon(result, base)
|
||||
let isPure = result.sym != nil and sfPure in result.sym.flags
|
||||
var hasNull = false
|
||||
for i in countup(1, sonsLen(n) - 1):
|
||||
case n.sons[i].kind
|
||||
of nkEnumFieldDef:
|
||||
@@ -74,6 +75,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
|
||||
else: illFormedAst(n)
|
||||
e.typ = result
|
||||
e.position = int(counter)
|
||||
if e.position == 0: hasNull = true
|
||||
if result.sym != nil and sfExported in result.sym.flags:
|
||||
incl(e.flags, sfUsed)
|
||||
incl(e.flags, sfExported)
|
||||
@@ -81,6 +83,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
|
||||
addSon(result.n, newSymNode(e))
|
||||
if sfGenSym notin e.flags and not isPure: addDecl(c, e)
|
||||
inc(counter)
|
||||
if not hasNull: incl(result.flags, tfNeedsInit)
|
||||
|
||||
proc semSet(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = newOrPrevType(tySet, prev, c)
|
||||
@@ -168,7 +171,14 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
|
||||
proc semRange(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = nil
|
||||
if sonsLen(n) == 2:
|
||||
if isRange(n[1]): result = semRangeAux(c, n[1], prev)
|
||||
if isRange(n[1]):
|
||||
result = semRangeAux(c, n[1], prev)
|
||||
let n = result.n
|
||||
if n.sons[0].kind in {nkCharLit..nkUInt64Lit}:
|
||||
if n.sons[0].intVal > 0 or n.sons[1].intVal < 0:
|
||||
incl(result.flags, tfNeedsInit)
|
||||
elif n.sons[0].floatVal > 0.0 or n.sons[1].floatVal < 0.0:
|
||||
incl(result.flags, tfNeedsInit)
|
||||
else:
|
||||
LocalError(n.sons[0].info, errRangeExpected)
|
||||
result = newOrPrevType(tyError, prev, c)
|
||||
@@ -386,34 +396,34 @@ proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int,
|
||||
swap(branch.sons[L-2], branch.sons[L-1])
|
||||
checkForOverlap(c, t, i, branchIndex)
|
||||
|
||||
proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int,
|
||||
father: PNode, rectype: PSym)
|
||||
proc semRecordCase(c: PContext, n: PNode, check: var TIntSet, pos: var int,
|
||||
father: PNode, rectype: PSym) =
|
||||
proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int,
|
||||
father: PNode, rectype: PType)
|
||||
proc semRecordCase(c: PContext, n: PNode, check: var TIntSet, pos: var int,
|
||||
father: PNode, rectype: PType) =
|
||||
var a = copyNode(n)
|
||||
checkMinSonsLen(n, 2)
|
||||
semRecordNodeAux(c, n.sons[0], check, pos, a, rectype)
|
||||
if a.sons[0].kind != nkSym:
|
||||
if a.sons[0].kind != nkSym:
|
||||
internalError("semRecordCase: discriminant is no symbol")
|
||||
return
|
||||
incl(a.sons[0].sym.flags, sfDiscriminant)
|
||||
var covered: biggestInt = 0
|
||||
var typ = skipTypes(a.sons[0].Typ, abstractVar-{tyTypeDesc})
|
||||
if not isOrdinalType(typ):
|
||||
if not isOrdinalType(typ):
|
||||
LocalError(n.info, errSelectorMustBeOrdinal)
|
||||
elif firstOrd(typ) < 0:
|
||||
elif firstOrd(typ) < 0:
|
||||
LocalError(n.info, errOrdXMustNotBeNegative, a.sons[0].sym.name.s)
|
||||
elif lengthOrd(typ) > 0x00007FFF:
|
||||
elif lengthOrd(typ) > 0x00007FFF:
|
||||
LocalError(n.info, errLenXinvalid, a.sons[0].sym.name.s)
|
||||
var chckCovered = true
|
||||
for i in countup(1, sonsLen(n) - 1):
|
||||
for i in countup(1, sonsLen(n) - 1):
|
||||
var b = copyTree(n.sons[i])
|
||||
addSon(a, b)
|
||||
case n.sons[i].kind
|
||||
of nkOfBranch:
|
||||
of nkOfBranch:
|
||||
checkMinSonsLen(b, 2)
|
||||
semCaseBranch(c, a, b, i, covered)
|
||||
of nkElse:
|
||||
of nkElse:
|
||||
chckCovered = false
|
||||
checkSonsLen(b, 1)
|
||||
else: illFormedAst(n)
|
||||
@@ -424,7 +434,7 @@ proc semRecordCase(c: PContext, n: PNode, check: var TIntSet, pos: var int,
|
||||
addSon(father, a)
|
||||
|
||||
proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int,
|
||||
father: PNode, rectype: PSym) =
|
||||
father: PNode, rectype: PType) =
|
||||
if n == nil: return
|
||||
case n.kind
|
||||
of nkRecWhen:
|
||||
@@ -463,7 +473,7 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int,
|
||||
semRecordCase(c, n, check, pos, father, rectype)
|
||||
of nkNilLit:
|
||||
if father.kind != nkRecList: addSon(father, newNodeI(nkRecList, n.info))
|
||||
of nkRecList:
|
||||
of nkRecList:
|
||||
# attempt to keep the nesting at a sane level:
|
||||
var a = if father.kind == nkRecList: father else: copyNode(n)
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
@@ -473,7 +483,7 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int,
|
||||
checkMinSonsLen(n, 3)
|
||||
var length = sonsLen(n)
|
||||
var a: PNode
|
||||
if father.kind != nkRecList and length >= 4: a = newNodeI(nkRecList, n.info)
|
||||
if father.kind != nkRecList and length>=4: a = newNodeI(nkRecList, n.info)
|
||||
else: a = ast.emptyNode
|
||||
if n.sons[length-1].kind != nkEmpty:
|
||||
localError(n.sons[length-1].info, errInitHereNotAllowed)
|
||||
@@ -483,17 +493,19 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int,
|
||||
typ = errorType(c)
|
||||
else:
|
||||
typ = semTypeNode(c, n.sons[length-2], nil)
|
||||
propagateToOwner(rectype, typ)
|
||||
let rec = rectype.sym
|
||||
for i in countup(0, sonsLen(n)-3):
|
||||
var f = semIdentWithPragma(c, skField, n.sons[i], {sfExported})
|
||||
suggestSym(n.sons[i], f)
|
||||
f.typ = typ
|
||||
f.position = pos
|
||||
if (rectype != nil) and ({sfImportc, sfExportc} * rectype.flags != {}) and
|
||||
if (rec != nil) and ({sfImportc, sfExportc} * rec.flags != {}) and
|
||||
(f.loc.r == nil):
|
||||
f.loc.r = toRope(f.name.s)
|
||||
f.flags = f.flags + ({sfImportc, sfExportc} * rectype.flags)
|
||||
f.flags = f.flags + ({sfImportc, sfExportc} * rec.flags)
|
||||
inc(pos)
|
||||
if ContainsOrIncl(check, f.name.id):
|
||||
if ContainsOrIncl(check, f.name.id):
|
||||
localError(n.sons[i].info, errAttemptToRedefine, f.name.s)
|
||||
if a.kind == nkEmpty: addSon(father, newSymNode(f))
|
||||
else: addSon(a, newSymNode(f))
|
||||
@@ -502,20 +514,20 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int,
|
||||
else: illFormedAst(n)
|
||||
|
||||
proc addInheritedFieldsAux(c: PContext, check: var TIntSet, pos: var int,
|
||||
n: PNode) =
|
||||
n: PNode) =
|
||||
case n.kind
|
||||
of nkRecCase:
|
||||
of nkRecCase:
|
||||
if (n.sons[0].kind != nkSym): InternalError(n.info, "addInheritedFieldsAux")
|
||||
addInheritedFieldsAux(c, check, pos, n.sons[0])
|
||||
for i in countup(1, sonsLen(n) - 1):
|
||||
for i in countup(1, sonsLen(n) - 1):
|
||||
case n.sons[i].kind
|
||||
of nkOfBranch, nkElse:
|
||||
of nkOfBranch, nkElse:
|
||||
addInheritedFieldsAux(c, check, pos, lastSon(n.sons[i]))
|
||||
else: internalError(n.info, "addInheritedFieldsAux(record case branch)")
|
||||
of nkRecList:
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
of nkRecList:
|
||||
for i in countup(0, sonsLen(n) - 1):
|
||||
addInheritedFieldsAux(c, check, pos, n.sons[i])
|
||||
of nkSym:
|
||||
of nkSym:
|
||||
Incl(check, n.sym.name.id)
|
||||
inc(pos)
|
||||
else: InternalError(n.info, "addInheritedFieldsAux()")
|
||||
@@ -553,7 +565,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
result = newOrPrevType(tyObject, prev, c)
|
||||
rawAddSon(result, base)
|
||||
result.n = newNodeI(nkRecList, n.info)
|
||||
semRecordNodeAux(c, n.sons[2], check, pos, result.n, result.sym)
|
||||
semRecordNodeAux(c, n.sons[2], check, pos, result.n, result)
|
||||
if n.sons[0].kind != nkEmpty:
|
||||
# dummy symbol for `pragma`:
|
||||
var s = newSymS(skType, newIdentNode(getIdent("dummy"), n.info), c)
|
||||
@@ -853,6 +865,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
|
||||
if result.kind in NilableTypes and n.sons[2].kind == nkNilLit:
|
||||
result = freshType(result, prev)
|
||||
result.flags.incl(tfNotNil)
|
||||
result.flags.incl(tfNeedsInit)
|
||||
else:
|
||||
LocalError(n.info, errGenerated, "invalid type")
|
||||
else:
|
||||
|
||||
@@ -152,6 +152,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType =
|
||||
x = lookupTypeVar(cl, x)
|
||||
if header == nil: header = copyType(t, t.owner, false)
|
||||
header.sons[i] = x
|
||||
propagateToOwner(header, x)
|
||||
#idTablePut(cl.typeMap, body.sons[i-1], x)
|
||||
if header != nil:
|
||||
# search again after first pass:
|
||||
@@ -170,6 +171,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType =
|
||||
var x = replaceTypeVarsT(cl, t.sons[i])
|
||||
assert x.kind != tyGenericInvokation
|
||||
header.sons[i] = x
|
||||
propagateToOwner(header, x)
|
||||
idTablePut(cl.typeMap, body.sons[i-1], x)
|
||||
|
||||
for i in countup(1, sonsLen(t) - 1):
|
||||
|
||||
@@ -504,8 +504,8 @@ proc transformCase(c: PTransf, n: PNode): PTransNode =
|
||||
result.add(elseBranch)
|
||||
elif result.Pnode.lastSon.kind != nkElse and not (
|
||||
skipTypes(n.sons[0].Typ, abstractVarRange).Kind in
|
||||
{tyInt..tyInt64, tyChar, tyEnum}):
|
||||
# fix a stupid code gen bug by normalizing:
|
||||
{tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32}):
|
||||
# fix a stupid code gen bug by normalizing:
|
||||
var elseBranch = newTransNode(nkElse, n.info, 1)
|
||||
elseBranch[0] = newTransNode(nkNilLit, n.info, 0)
|
||||
add(result, elseBranch)
|
||||
@@ -704,7 +704,7 @@ proc transformBody*(module: PSym, n: PNode, prc: PSym): PNode =
|
||||
if nfTransf in n.flags or prc.kind in {skTemplate, skMacro}:
|
||||
result = n
|
||||
else:
|
||||
when useEffectSystem: trackProc(prc, n)
|
||||
#when useEffectSystem: trackProc(prc, n)
|
||||
var c = openTransf(module, "")
|
||||
result = processTransf(c, n)
|
||||
if prc.kind != skMacro:
|
||||
@@ -713,6 +713,7 @@ proc transformBody*(module: PSym, n: PNode, prc: PSym): PNode =
|
||||
if prc.kind == skIterator and prc.typ.callConv == ccClosure:
|
||||
result = lambdalifting.liftIterator(prc, result)
|
||||
incl(result.flags, nfTransf)
|
||||
when useEffectSystem: trackProc(prc, result)
|
||||
|
||||
proc transformStmt*(module: PSym, n: PNode): PNode =
|
||||
if nfTransf in n.flags:
|
||||
|
||||
@@ -93,8 +93,7 @@ proc getOpSym*(op: PNode): PSym =
|
||||
|
||||
proc getMagic*(op: PNode): TMagic =
|
||||
case op.kind
|
||||
of nkCall, nkHiddenCallConv, nkCommand, nkCallStrLit, nkPrefix, nkPostfix,
|
||||
nkInfix:
|
||||
of nkCallKinds:
|
||||
case op.sons[0].Kind
|
||||
of nkSym: result = op.sons[0].sym.magic
|
||||
else: result = mNone
|
||||
|
||||
@@ -41,7 +41,7 @@ type
|
||||
|
||||
wImmediate, wDestructor, wImportCpp, wImportObjC,
|
||||
wImportCompilerProc,
|
||||
wImportc, wExportc, wIncompleteStruct,
|
||||
wImportc, wExportc, wIncompleteStruct, wRequiresInit,
|
||||
wAlign, wNodecl, wPure, wSideeffect, wHeader,
|
||||
wNosideeffect, wNoreturn, wMerge, wLib, wDynlib, wCompilerproc, wProcVar,
|
||||
wFatal, wError, wWarning, wHint, wLine, wPush, wPop, wDefine, wUndef,
|
||||
@@ -122,7 +122,7 @@ const
|
||||
|
||||
"immediate", "destructor", "importcpp", "importobjc",
|
||||
"importcompilerproc", "importc", "exportc", "incompletestruct",
|
||||
"align", "nodecl", "pure", "sideeffect",
|
||||
"requiresinit", "align", "nodecl", "pure", "sideeffect",
|
||||
"header", "nosideeffect", "noreturn", "merge", "lib", "dynlib",
|
||||
"compilerproc", "procvar", "fatal", "error", "warning", "hint", "line",
|
||||
"push", "pop", "define", "undef", "linedir", "stacktrace", "linetrace",
|
||||
|
||||
@@ -327,36 +327,36 @@ Numerical constants
|
||||
|
||||
`Numerical constants`:idx: are of a single type and have the form::
|
||||
|
||||
hexdigit ::= digit | 'A'..'F' | 'a'..'f'
|
||||
octdigit ::= '0'..'7'
|
||||
bindigit ::= '0'..'1'
|
||||
HEX_LIT ::= '0' ('x' | 'X' ) hexdigit ( ['_'] hexdigit )*
|
||||
DEC_LIT ::= digit ( ['_'] digit )*
|
||||
OCT_LIT ::= '0o' octdigit ( ['_'] octdigit )*
|
||||
BIN_LIT ::= '0' ('b' | 'B' ) bindigit ( ['_'] bindigit )*
|
||||
hexdigit = digit | 'A'..'F' | 'a'..'f'
|
||||
octdigit = '0'..'7'
|
||||
bindigit = '0'..'1'
|
||||
HEX_LIT = '0' ('x' | 'X' ) hexdigit ( ['_'] hexdigit )*
|
||||
DEC_LIT = digit ( ['_'] digit )*
|
||||
OCT_LIT = '0o' octdigit ( ['_'] octdigit )*
|
||||
BIN_LIT = '0' ('b' | 'B' ) bindigit ( ['_'] bindigit )*
|
||||
|
||||
INT_LIT ::= HEX_LIT
|
||||
| DEC_LIT
|
||||
| OCT_LIT
|
||||
| BIN_LIT
|
||||
INT_LIT = HEX_LIT
|
||||
| DEC_LIT
|
||||
| OCT_LIT
|
||||
| BIN_LIT
|
||||
|
||||
INT8_LIT ::= INT_LIT ['\''] ('i' | 'I') '8'
|
||||
INT16_LIT ::= INT_LIT ['\''] ('i' | 'I') '16'
|
||||
INT32_LIT ::= INT_LIT ['\''] ('i' | 'I') '32'
|
||||
INT64_LIT ::= INT_LIT ['\''] ('i' | 'I') '64'
|
||||
INT8_LIT = INT_LIT ['\''] ('i' | 'I') '8'
|
||||
INT16_LIT = INT_LIT ['\''] ('i' | 'I') '16'
|
||||
INT32_LIT = INT_LIT ['\''] ('i' | 'I') '32'
|
||||
INT64_LIT = INT_LIT ['\''] ('i' | 'I') '64'
|
||||
|
||||
UINT8_LIT ::= INT_LIT ['\''] ('u' | 'U')
|
||||
UINT8_LIT ::= INT_LIT ['\''] ('u' | 'U') '8'
|
||||
UINT16_LIT ::= INT_LIT ['\''] ('u' | 'U') '16'
|
||||
UINT32_LIT ::= INT_LIT ['\''] ('u' | 'U') '32'
|
||||
UINT64_LIT ::= INT_LIT ['\''] ('u' | 'U') '64'
|
||||
UINT8_LIT = INT_LIT ['\''] ('u' | 'U')
|
||||
UINT8_LIT = INT_LIT ['\''] ('u' | 'U') '8'
|
||||
UINT16_LIT = INT_LIT ['\''] ('u' | 'U') '16'
|
||||
UINT32_LIT = INT_LIT ['\''] ('u' | 'U') '32'
|
||||
UINT64_LIT = INT_LIT ['\''] ('u' | 'U') '64'
|
||||
|
||||
exponent ::= ('e' | 'E' ) ['+' | '-'] digit ( ['_'] digit )*
|
||||
FLOAT_LIT ::= digit (['_'] digit)* ('.' (['_'] digit)* [exponent] |exponent)
|
||||
FLOAT32_LIT ::= HEX_LIT '\'' ('f'|'F') '32'
|
||||
| (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '32'
|
||||
FLOAT64_LIT ::= HEX_LIT '\'' ('f'|'F') '64'
|
||||
| (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '64'
|
||||
exponent = ('e' | 'E' ) ['+' | '-'] digit ( ['_'] digit )*
|
||||
FLOAT_LIT = digit (['_'] digit)* ('.' (['_'] digit)* [exponent] |exponent)
|
||||
FLOAT32_LIT = HEX_LIT '\'' ('f'|'F') '32'
|
||||
| (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '32'
|
||||
FLOAT64_LIT = HEX_LIT '\'' ('f'|'F') '64'
|
||||
| (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '64'
|
||||
|
||||
|
||||
As can be seen in the productions, numerical constants can contain underscores
|
||||
@@ -1818,6 +1818,24 @@ If a proc is annotated with the ``noinit`` pragma this refers to its implicit
|
||||
proc returnUndefinedValue: int {.noinit.} = nil
|
||||
|
||||
|
||||
The implicit initialization can be also prevented by the `requiresInit`:idx:
|
||||
type pragma. The compiler requires an explicit initialization then. However
|
||||
it does a `control flow analysis`:idx: to prove the variable has been
|
||||
initialized and does not rely on syntactic properties:
|
||||
|
||||
.. code-block:: nimrod
|
||||
type
|
||||
TMyObject = object {.requiresInit.}
|
||||
|
||||
proc p() =
|
||||
# the following is valid:
|
||||
var x: TMyObject
|
||||
if someCondition():
|
||||
x = a()
|
||||
else:
|
||||
x = a()
|
||||
use x
|
||||
|
||||
let statement
|
||||
-------------
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ on the different supported platforms. It is not a definition of the Nimrod
|
||||
programming language (therefore is the `manual <manual.html>`_).
|
||||
|
||||
Nimrod is free software; it is licensed under the
|
||||
`GNU General Public License <gpl.html>`_.
|
||||
`MIT License <http://www.opensource.org/licenses/mit-license.php>`_.
|
||||
|
||||
|
||||
Compiler Usage
|
||||
|
||||
@@ -338,24 +338,24 @@ when not defined(JS):
|
||||
const
|
||||
weekDays: array [0..6, TWeekDay] = [
|
||||
dSun, dMon, dTue, dWed, dThu, dFri, dSat]
|
||||
result.second = int(tm.second)
|
||||
result.minute = int(tm.minute)
|
||||
result.hour = int(tm.hour)
|
||||
result.monthday = int(tm.monthday)
|
||||
result.month = TMonth(tm.month)
|
||||
result.year = tm.year + 1900'i32
|
||||
result.weekday = weekDays[int(tm.weekDay)]
|
||||
result.yearday = int(tm.yearday)
|
||||
result.isDST = tm.isDST > 0
|
||||
if local:
|
||||
if result.isDST:
|
||||
result.tzname = getTzname().DST
|
||||
else:
|
||||
result.tzname = getTzname().nonDST
|
||||
else:
|
||||
result.tzname = "UTC"
|
||||
|
||||
result.timezone = if local: getTimezone() else: 0
|
||||
TTimeInfo(second: int(tm.second),
|
||||
minute: int(tm.minute),
|
||||
hour: int(tm.hour),
|
||||
monthday: int(tm.monthday),
|
||||
month: TMonth(tm.month),
|
||||
year: tm.year + 1900'i32,
|
||||
weekday: weekDays[int(tm.weekDay)],
|
||||
yearday: int(tm.yearday),
|
||||
isDST: tm.isDST > 0,
|
||||
tzname: if local:
|
||||
if tm.isDST > 0:
|
||||
getTzname().DST
|
||||
else:
|
||||
getTzname().nonDST
|
||||
else:
|
||||
"UTC",
|
||||
timezone: if local: getTimezone() else: 0
|
||||
)
|
||||
|
||||
proc timeInfoToTM(t: TTimeInfo): structTM =
|
||||
const
|
||||
|
||||
36
tests/reject/tuninit1.nim
Normal file
36
tests/reject/tuninit1.nim
Normal file
@@ -0,0 +1,36 @@
|
||||
discard """
|
||||
errormsg: "'y' might not have been initialized"
|
||||
line:28
|
||||
"""
|
||||
|
||||
import strutils
|
||||
|
||||
{.warning[Uninit]:on.}
|
||||
|
||||
proc p =
|
||||
var x, y, z: int
|
||||
if stdin.readLine == "true":
|
||||
x = 34
|
||||
|
||||
while false:
|
||||
y = 999
|
||||
break
|
||||
|
||||
while true:
|
||||
if x == 12: break
|
||||
y = 9999
|
||||
|
||||
try:
|
||||
z = parseInt("1233")
|
||||
except E_Base:
|
||||
case x
|
||||
of 34: z = 123
|
||||
of 13: z = 34
|
||||
else: z = 8
|
||||
else:
|
||||
y = 3444
|
||||
x = 3111
|
||||
z = 0
|
||||
echo x, y, z
|
||||
|
||||
p()
|
||||
3
todo.txt
3
todo.txt
@@ -2,7 +2,8 @@ version 0.9.4
|
||||
=============
|
||||
|
||||
- make 'bind' default for templates and introduce 'mixin';
|
||||
- implement full 'not nil' checking; range[1..3] needs the same mechanism
|
||||
- test 'not nil' checking more
|
||||
- prove field accesses; prove array accesses
|
||||
- special rule for ``[]=``
|
||||
- ``=`` should be overloadable; requires specialization for ``=``; general
|
||||
lift mechanism in the compiler is already implemented for 'fields'
|
||||
|
||||
@@ -227,9 +227,9 @@ when isMainModule:
|
||||
|
||||
#echo(createRules())
|
||||
|
||||
prepDeb("nimrod", "0.8.14", "Dominik Picheta", "morfeusz8@gmail.com",
|
||||
prepDeb("nimrod", "0.9.2", "Dominik Picheta", "morfeusz8@gmail.com",
|
||||
"The Nimrod compiler", "Compiler for the Nimrod programming language",
|
||||
@[("bin/nimrod", "gpl2"), ("lib/*", "lgpl")],
|
||||
@[("bin/nimrod", "MIT"), ("lib/*", "MIT")],
|
||||
@["bin/nimrod"], @["config/*"], @["doc/*"], @["lib/*"],
|
||||
"gcc (>= 4:4.3.2)", "gcc (>= 4:4.3.2)")
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ Nimrod plays nice with others
|
||||
* **The Nimrod Compiler can also generate C++ or Objective C for easier
|
||||
interfacing.**
|
||||
* There are lots of bindings: for example, bindings to GTK2, the Windows API,
|
||||
the POSIX API, OpenGL, SDL, Cario, Python, Lua, TCL, X11, libzip, PCRE,
|
||||
the POSIX API, OpenGL, SDL, Cairo, Python, Lua, TCL, X11, libzip, PCRE,
|
||||
libcurl, mySQL and SQLite are included in the standard distribution.
|
||||
* A C to Nimrod conversion utility: New bindings to C libraries are easily
|
||||
generated by ``c2nim``.
|
||||
|
||||
@@ -36,7 +36,7 @@ Language Additions
|
||||
|
||||
- Arrays can now be declared with a single integer literal ``N`` instead of a
|
||||
range; the range is then ``0..N-1``.
|
||||
|
||||
- Added ``requiresInit`` pragma to enforce explicit initialization.
|
||||
|
||||
|
||||
2013-05-20 New website design!
|
||||
|
||||
Reference in New Issue
Block a user