implemented dataflow analysis; activate via --warning[Uninit]:on
This commit is contained in:
parent
f7c0cc976d
commit
2aaa8f7909
6 changed files with 185 additions and 26 deletions
|
|
@ -106,7 +106,7 @@ type
|
||||||
warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored,
|
warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored,
|
||||||
warnNilStatement, warnAnalysisLoophole,
|
warnNilStatement, warnAnalysisLoophole,
|
||||||
warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure,
|
warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure,
|
||||||
warnEachIdentIsTuple, warnShadowIdent, warnUser,
|
warnEachIdentIsTuple, warnShadowIdent, warnUninit, warnUser,
|
||||||
hintSuccess, hintSuccessX,
|
hintSuccess, hintSuccessX,
|
||||||
hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
|
hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
|
||||||
hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled,
|
hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled,
|
||||||
|
|
@ -355,6 +355,7 @@ const
|
||||||
warnImplicitClosure: "implicit closure convention: '$1' [ImplicitClosure]",
|
warnImplicitClosure: "implicit closure convention: '$1' [ImplicitClosure]",
|
||||||
warnEachIdentIsTuple: "each identifier is a tuple [EachIdentIsTuple]",
|
warnEachIdentIsTuple: "each identifier is a tuple [EachIdentIsTuple]",
|
||||||
warnShadowIdent: "shadowed identifier: '$1' [ShadowIdent]",
|
warnShadowIdent: "shadowed identifier: '$1' [ShadowIdent]",
|
||||||
|
warnUninit: "read from potentially uninitialized variable: '$1' [Uninit]",
|
||||||
warnUser: "$1 [User]",
|
warnUser: "$1 [User]",
|
||||||
hintSuccess: "operation successful [Success]",
|
hintSuccess: "operation successful [Success]",
|
||||||
hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#) [SuccessX]",
|
hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#) [SuccessX]",
|
||||||
|
|
@ -374,14 +375,15 @@ const
|
||||||
hintUser: "$1 [User]"]
|
hintUser: "$1 [User]"]
|
||||||
|
|
||||||
const
|
const
|
||||||
WarningsToStr*: array[0..19, string] = ["CannotOpenFile", "OctalEscape",
|
WarningsToStr*: array[0..20, string] = ["CannotOpenFile", "OctalEscape",
|
||||||
"XIsNeverRead", "XmightNotBeenInit",
|
"XIsNeverRead", "XmightNotBeenInit",
|
||||||
"Deprecated", "ConfigDeprecated",
|
"Deprecated", "ConfigDeprecated",
|
||||||
"SmallLshouldNotBeUsed", "UnknownMagic",
|
"SmallLshouldNotBeUsed", "UnknownMagic",
|
||||||
"RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported",
|
"RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported",
|
||||||
"CommentXIgnored", "NilStmt",
|
"CommentXIgnored", "NilStmt",
|
||||||
"AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap",
|
"AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap",
|
||||||
"ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", "User"]
|
"ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", "Uninit",
|
||||||
|
"User"]
|
||||||
|
|
||||||
HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong",
|
HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong",
|
||||||
"XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
|
"XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
|
||||||
|
|
@ -512,7 +514,8 @@ proc raiseRecoverableError*(msg: string) {.noinline, noreturn.} =
|
||||||
proc sourceLine*(i: TLineInfo): PRope
|
proc sourceLine*(i: TLineInfo): PRope
|
||||||
|
|
||||||
var
|
var
|
||||||
gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent}
|
gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} -
|
||||||
|
{warnShadowIdent, warnUninit}
|
||||||
gErrorCounter*: int = 0 # counts the number of errors
|
gErrorCounter*: int = 0 # counts the number of errors
|
||||||
gHintCounter*: int = 0
|
gHintCounter*: int = 0
|
||||||
gWarnCounter*: int = 0
|
gWarnCounter*: int = 0
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import
|
||||||
# way had some inherent problems. Performs:
|
# way had some inherent problems. Performs:
|
||||||
#
|
#
|
||||||
# * effect+exception tracking
|
# * effect+exception tracking
|
||||||
|
# * "usage before definition" checking
|
||||||
# * checks for invalid usages of compiletime magics (not implemented)
|
# * checks for invalid usages of compiletime magics (not implemented)
|
||||||
# * checks for invalid usages of PNimNode (not implemented)
|
# * checks for invalid usages of PNimNode (not implemented)
|
||||||
# * later: will do an escape analysis for closures at least
|
# * later: will do an escape analysis for closures at least
|
||||||
|
|
@ -42,14 +43,6 @@ import
|
||||||
# an array and filling it in parallel should be supported but is not easily
|
# an array and filling it in parallel should be supported but is not easily
|
||||||
# done: It essentially requires a built-in 'indexSplit' operation and dependent
|
# done: It essentially requires a built-in 'indexSplit' operation and dependent
|
||||||
# typing.
|
# typing.
|
||||||
|
|
||||||
when false:
|
|
||||||
proc sem2call(c: PContext, n: PNode): PNode =
|
|
||||||
assert n.kind in nkCallKinds
|
|
||||||
|
|
||||||
proc sem2sym(c: PContext, n: PNode): PNode =
|
|
||||||
assert n.kind == nkSym
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------ exception and tag tracking -------------------------
|
# ------------------------ exception and tag tracking -------------------------
|
||||||
|
|
||||||
|
|
@ -80,9 +73,44 @@ type
|
||||||
tags: PNode # list of tags
|
tags: PNode # list of tags
|
||||||
bottom: int
|
bottom: int
|
||||||
owner: PSym
|
owner: PSym
|
||||||
|
init: seq[int] # list of initialized variables
|
||||||
|
# coming soon: "guard" tracking for 'let' variables
|
||||||
|
|
||||||
PEffects = var TEffects
|
PEffects = var TEffects
|
||||||
|
|
||||||
|
proc isLocalVar(a: PEffects, s: PSym): bool =
|
||||||
|
s.kind in {skVar, skResult} and sfGlobal notin s.flags and s.owner == a.owner
|
||||||
|
|
||||||
|
proc initVar(a: PEffects, n: PNode) =
|
||||||
|
if n.kind != nkSym: return
|
||||||
|
let s = n.sym
|
||||||
|
if isLocalVar(a, s):
|
||||||
|
for x in a.init:
|
||||||
|
if x == s.id: return
|
||||||
|
a.init.add s.id
|
||||||
|
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
Message(n.info, errGenerated,
|
||||||
|
"read from potentially uninitialized variable: '$1'" % s.name.s)
|
||||||
|
# prevent superfluous warnings about the same variable:
|
||||||
|
a.init.add s.id
|
||||||
|
|
||||||
|
type
|
||||||
|
TIntersection = seq[tuple[id, count: int]] # a simple count table
|
||||||
|
|
||||||
|
proc addToIntersection(inter: var TIntersection, s: int) =
|
||||||
|
for j in 0.. <inter.len:
|
||||||
|
if s == inter[j].id:
|
||||||
|
inc inter[j].count
|
||||||
|
return
|
||||||
|
inter.add((id: s, count: 1))
|
||||||
|
|
||||||
proc throws(tracked, n: PNode) =
|
proc throws(tracked, n: PNode) =
|
||||||
if n.typ == nil or n.typ.kind != tyError: tracked.add n
|
if n.typ == nil or n.typ.kind != tyError: tracked.add n
|
||||||
|
|
||||||
|
|
@ -158,21 +186,43 @@ proc track(tracked: PEffects, n: PNode)
|
||||||
proc trackTryStmt(tracked: PEffects, n: PNode) =
|
proc trackTryStmt(tracked: PEffects, n: PNode) =
|
||||||
let oldBottom = tracked.bottom
|
let oldBottom = tracked.bottom
|
||||||
tracked.bottom = tracked.exc.len
|
tracked.bottom = tracked.exc.len
|
||||||
track(tracked, n.sons[0])
|
|
||||||
|
let oldState = tracked.init.len
|
||||||
|
var inter: TIntersection = @[]
|
||||||
|
|
||||||
|
track(tracked, n.sons[0])
|
||||||
|
for i in oldState.. <tracked.init.len:
|
||||||
|
addToIntersection(inter, tracked.init[i])
|
||||||
|
|
||||||
|
var branches = 1
|
||||||
|
var hasFinally = false
|
||||||
for i in 1 .. < n.len:
|
for i in 1 .. < n.len:
|
||||||
let b = n.sons[i]
|
let b = n.sons[i]
|
||||||
let blen = sonsLen(b)
|
let blen = sonsLen(b)
|
||||||
if b.kind == nkExceptBranch:
|
if b.kind == nkExceptBranch:
|
||||||
|
inc branches
|
||||||
if blen == 1:
|
if blen == 1:
|
||||||
catchesAll(tracked)
|
catchesAll(tracked)
|
||||||
else:
|
else:
|
||||||
for j in countup(0, blen - 2):
|
for j in countup(0, blen - 2):
|
||||||
assert(b.sons[j].kind == nkType)
|
assert(b.sons[j].kind == nkType)
|
||||||
catches(tracked, b.sons[j].typ)
|
catches(tracked, b.sons[j].typ)
|
||||||
|
|
||||||
|
setLen(tracked.init, oldState)
|
||||||
|
track(tracked, b.sons[blen-1])
|
||||||
|
for i in oldState.. <tracked.init.len:
|
||||||
|
addToIntersection(inter, tracked.init[i])
|
||||||
else:
|
else:
|
||||||
assert b.kind == nkFinally
|
assert b.kind == nkFinally
|
||||||
track(tracked, b.sons[blen-1])
|
setLen(tracked.init, oldState)
|
||||||
|
track(tracked, b.sons[blen-1])
|
||||||
|
hasFinally = true
|
||||||
|
|
||||||
tracked.bottom = oldBottom
|
tracked.bottom = oldBottom
|
||||||
|
if not hasFinally:
|
||||||
|
setLen(tracked.init, oldState)
|
||||||
|
for id, count in items(inter):
|
||||||
|
if count == branches: tracked.init.add id
|
||||||
|
|
||||||
proc isIndirectCall(n: PNode, owner: PSym): bool =
|
proc isIndirectCall(n: PNode, owner: PSym): bool =
|
||||||
# we don't count f(...) as an indirect call if 'f' is an parameter.
|
# we don't count f(...) as an indirect call if 'f' is an parameter.
|
||||||
|
|
@ -263,11 +313,79 @@ proc trackOperand(tracked: PEffects, n: PNode) =
|
||||||
mergeEffects(tracked, effectList.sons[exceptionEffects], n)
|
mergeEffects(tracked, effectList.sons[exceptionEffects], n)
|
||||||
mergeTags(tracked, effectList.sons[tagEffects], n)
|
mergeTags(tracked, effectList.sons[tagEffects], n)
|
||||||
|
|
||||||
|
proc trackCase(tracked: PEffects, n: PNode) =
|
||||||
|
track(tracked, n.sons[0])
|
||||||
|
let oldState = tracked.init.len
|
||||||
|
var inter: TIntersection = @[]
|
||||||
|
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])
|
||||||
|
for i in oldState.. <tracked.init.len:
|
||||||
|
addToIntersection(inter, tracked.init[i])
|
||||||
|
let exh = case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind
|
||||||
|
of tyFloat..tyFloat128, tyString:
|
||||||
|
lastSon(n).kind == nkElse
|
||||||
|
else:
|
||||||
|
true
|
||||||
|
setLen(tracked.init, oldState)
|
||||||
|
if exh:
|
||||||
|
for id, count in items(inter):
|
||||||
|
if count == n.len-1: tracked.init.add id
|
||||||
|
# else we can't merge
|
||||||
|
|
||||||
|
proc trackIf(tracked: PEffects, n: PNode) =
|
||||||
|
track(tracked, n.sons[0].sons[0])
|
||||||
|
let oldState = tracked.init.len
|
||||||
|
|
||||||
|
var inter: TIntersection = @[]
|
||||||
|
track(tracked, n.sons[0].sons[1])
|
||||||
|
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.init, oldState)
|
||||||
|
for i in 0 .. <branch.len:
|
||||||
|
track(tracked, branch.sons[i])
|
||||||
|
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
|
||||||
|
# else we can't merge as it is not exhaustive
|
||||||
|
|
||||||
|
proc trackBlock(tracked: PEffects, n: PNode) =
|
||||||
|
if n.kind in {nkStmtList, nkStmtListExpr}:
|
||||||
|
var oldState = -1
|
||||||
|
for i in 0.. <n.len:
|
||||||
|
if hasSubnodeWith(n.sons[i], nkBreakStmt):
|
||||||
|
# block:
|
||||||
|
# x = def
|
||||||
|
# if ...: ... break # some nested break
|
||||||
|
# y = def
|
||||||
|
# --> 'y' not defined after block!
|
||||||
|
if oldState < 0: oldState = tracked.init.len
|
||||||
|
track(tracked, n.sons[i])
|
||||||
|
if oldState > 0: setLen(tracked.init, oldState)
|
||||||
|
else:
|
||||||
|
track(tracked, n)
|
||||||
|
|
||||||
|
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 track(tracked: PEffects, n: PNode) =
|
proc track(tracked: PEffects, n: PNode) =
|
||||||
case n.kind
|
case n.kind
|
||||||
|
of nkSym:
|
||||||
|
useVar(tracked, n)
|
||||||
of nkRaiseStmt:
|
of nkRaiseStmt:
|
||||||
n.sons[0].info = n.info
|
n.sons[0].info = n.info
|
||||||
throws(tracked.exc, n.sons[0])
|
throws(tracked.exc, n.sons[0])
|
||||||
|
for i in 0 .. <safeLen(n):
|
||||||
|
track(tracked, n.sons[i])
|
||||||
of nkCallKinds:
|
of nkCallKinds:
|
||||||
# p's effects are ours too:
|
# p's effects are ours too:
|
||||||
let a = n.sons[0]
|
let a = n.sons[0]
|
||||||
|
|
@ -287,16 +405,45 @@ proc track(tracked: PEffects, n: PNode) =
|
||||||
mergeEffects(tracked, effectList.sons[exceptionEffects], n)
|
mergeEffects(tracked, effectList.sons[exceptionEffects], n)
|
||||||
mergeTags(tracked, effectList.sons[tagEffects], 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])
|
||||||
of nkTryStmt:
|
if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize,
|
||||||
trackTryStmt(tracked, n)
|
mNewSeq, mShallowCopy}:
|
||||||
return
|
# may not look like an assignment, but it is:
|
||||||
of nkPragma:
|
initVar(tracked, n.sons[1])
|
||||||
trackPragmaStmt(tracked, n)
|
for i in 0 .. <safeLen(n):
|
||||||
return
|
track(tracked, n.sons[i])
|
||||||
of nkMacroDef, nkTemplateDef: return
|
of nkTryStmt: trackTryStmt(tracked, n)
|
||||||
else: nil
|
of nkPragma: trackPragmaStmt(tracked, n)
|
||||||
for i in 0 .. <safeLen(n):
|
of nkMacroDef, nkTemplateDef: discard
|
||||||
track(tracked, n.sons[i])
|
of nkAsgn, nkFastAsgn:
|
||||||
|
track(tracked, n.sons[1])
|
||||||
|
initVar(tracked, n.sons[0])
|
||||||
|
track(tracked, n.sons[0])
|
||||||
|
of nkVarSection:
|
||||||
|
for child in n:
|
||||||
|
if child.kind == nkIdentDefs and lastSon(child).kind != nkEmpty:
|
||||||
|
track(tracked, lastSon(child))
|
||||||
|
for i in 0 .. child.len-3: initVar(tracked, child.sons[i])
|
||||||
|
of nkCaseStmt: trackCase(tracked, n)
|
||||||
|
of nkIfStmt, nkIfExpr: trackIf(tracked, n)
|
||||||
|
of nkBlockStmt, nkBlockExpr: trackBlock(tracked, n.sons[1])
|
||||||
|
of nkWhileStmt:
|
||||||
|
track(tracked, n.sons[0])
|
||||||
|
# 'while true' loop?
|
||||||
|
if isTrue(n.sons[0]):
|
||||||
|
trackBlock(tracked, n.sons[1])
|
||||||
|
else:
|
||||||
|
# loop may never execute:
|
||||||
|
let oldState = tracked.init.len
|
||||||
|
track(tracked, n.sons[1])
|
||||||
|
setLen(tracked.init, oldState)
|
||||||
|
of nkForStmt, nkParForStmt:
|
||||||
|
# we are very conservative here and assume the loop is never executed:
|
||||||
|
let oldState = tracked.init.len
|
||||||
|
for i in 0 .. <len(n):
|
||||||
|
track(tracked, n.sons[i])
|
||||||
|
setLen(tracked.init, oldState)
|
||||||
|
else:
|
||||||
|
for i in 0 .. <safeLen(n): track(tracked, n.sons[i])
|
||||||
|
|
||||||
proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool) =
|
proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool) =
|
||||||
# check that any real exception is listed in 'spec'; mark those as used;
|
# check that any real exception is listed in 'spec'; mark those as used;
|
||||||
|
|
@ -362,6 +509,7 @@ proc trackProc*(s: PSym, body: PNode) =
|
||||||
t.exc = effects.sons[exceptionEffects]
|
t.exc = effects.sons[exceptionEffects]
|
||||||
t.tags = effects.sons[tagEffects]
|
t.tags = effects.sons[tagEffects]
|
||||||
t.owner = s
|
t.owner = s
|
||||||
|
t.init = @[]
|
||||||
track(t, body)
|
track(t, body)
|
||||||
|
|
||||||
let p = s.ast.sons[pragmasPos]
|
let p = s.ast.sons[pragmasPos]
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ proc semCase(c: PContext, n: PNode): PNode =
|
||||||
var typ = CommonTypeBegin
|
var typ = CommonTypeBegin
|
||||||
var hasElse = false
|
var hasElse = false
|
||||||
case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind
|
case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind
|
||||||
of tyInt..tyInt64, tyChar, tyEnum:
|
of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32:
|
||||||
chckCovered = true
|
chckCovered = true
|
||||||
of tyFloat..tyFloat128, tyString, tyError:
|
of tyFloat..tyFloat128, tyString, tyError:
|
||||||
nil
|
nil
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ proc parseInt(x: int8): int {.noSideEffect.} = nil
|
||||||
proc parseInt(x: TFile): int {.noSideEffect.} = nil
|
proc parseInt(x: TFile): int {.noSideEffect.} = nil
|
||||||
proc parseInt(x: char): int {.noSideEffect.} = nil
|
proc parseInt(x: char): int {.noSideEffect.} = nil
|
||||||
proc parseInt(x: int16): int {.noSideEffect.} = nil
|
proc parseInt(x: int16): int {.noSideEffect.} = nil
|
||||||
|
|
||||||
|
proc parseInt[T](x: T): int = echo x; 34
|
||||||
|
|
||||||
type
|
type
|
||||||
TParseInt = proc (x: string): int {.noSideEffect.}
|
TParseInt = proc (x: string): int {.noSideEffect.}
|
||||||
|
|
@ -33,3 +35,5 @@ type
|
||||||
|
|
||||||
proc bar[a,b](f: TFoo[a,b], x: a) = echo(x, " ", f.lorem, f.ipsum)
|
proc bar[a,b](f: TFoo[a,b], x: a) = echo(x, " ", f.lorem, f.ipsum)
|
||||||
proc bar[a,b](f: TFoo[a,b], x: b) = echo(x, " ", f.lorem, f.ipsum)
|
proc bar[a,b](f: TFoo[a,b], x: b) = echo(x, " ", f.lorem, f.ipsum)
|
||||||
|
|
||||||
|
discard parseInt[string]("yay")
|
||||||
|
|
|
||||||
2
todo.txt
2
todo.txt
|
|
@ -2,6 +2,7 @@ version 0.9.4
|
||||||
=============
|
=============
|
||||||
|
|
||||||
- make 'bind' default for templates and introduce 'mixin';
|
- make 'bind' default for templates and introduce 'mixin';
|
||||||
|
- implement full 'not nil' checking; range[1..3] needs the same mechanism
|
||||||
- special rule for ``[]=``
|
- special rule for ``[]=``
|
||||||
- ``=`` should be overloadable; requires specialization for ``=``; general
|
- ``=`` should be overloadable; requires specialization for ``=``; general
|
||||||
lift mechanism in the compiler is already implemented for 'fields'
|
lift mechanism in the compiler is already implemented for 'fields'
|
||||||
|
|
@ -31,7 +32,6 @@ version 0.9.x
|
||||||
=============
|
=============
|
||||||
|
|
||||||
- macros as type pragmas
|
- macros as type pragmas
|
||||||
- implement full 'not nil' checking; range[1..3] needs the same mechanism
|
|
||||||
- implicit deref for parameter matching
|
- implicit deref for parameter matching
|
||||||
- lazy overloading resolution:
|
- lazy overloading resolution:
|
||||||
* get rid of ``expr[typ]``, use perhaps ``static[typ]`` instead
|
* get rid of ``expr[typ]``, use perhaps ``static[typ]`` instead
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ Changes affecting backwards compatibility
|
||||||
Compiler Additions
|
Compiler Additions
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- The compiler can now warn about "uninitialized" variables. (There are no
|
||||||
|
real uninitialized variables in Nimrod as they are initialized to binary
|
||||||
|
zero). Activate via ``{.warning[Uninit]:on.}``.
|
||||||
|
|
||||||
|
|
||||||
Language Additions
|
Language Additions
|
||||||
------------------
|
------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue