diff options
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/cgen.nim | 32 | ||||
-rw-r--r-- | compiler/commands.nim | 2 | ||||
-rw-r--r-- | compiler/destroyer.nim | 170 | ||||
-rw-r--r-- | compiler/dfa.nim | 558 | ||||
-rw-r--r-- | compiler/docgen.nim | 90 | ||||
-rw-r--r-- | compiler/gorgeimpl.nim | 4 | ||||
-rw-r--r-- | compiler/msgs.nim | 4 | ||||
-rw-r--r-- | compiler/options.nim | 1 | ||||
-rw-r--r-- | compiler/sem.nim | 9 | ||||
-rw-r--r-- | compiler/semcall.nim | 28 | ||||
-rw-r--r-- | compiler/semexprs.nim | 27 | ||||
-rw-r--r-- | compiler/semmagic.nim | 5 | ||||
-rw-r--r-- | compiler/sempass2.nim | 12 | ||||
-rw-r--r-- | compiler/semstmts.nim | 31 | ||||
-rw-r--r-- | compiler/semtempl.nim | 13 | ||||
-rw-r--r-- | compiler/semtypes.nim | 6 | ||||
-rw-r--r-- | compiler/semtypinst.nim | 15 | ||||
-rw-r--r-- | compiler/sigmatch.nim | 2 | ||||
-rw-r--r-- | compiler/suggest.nim | 8 | ||||
-rw-r--r-- | compiler/types.nim | 9 | ||||
-rw-r--r-- | compiler/vm.nim | 8 | ||||
-rw-r--r-- | compiler/vmgen.nim | 6 |
22 files changed, 681 insertions, 359 deletions
diff --git a/compiler/cgen.nim b/compiler/cgen.nim index e4f16f4ed..2d9814621 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -321,7 +321,7 @@ proc resetLoc(p: BProc, loc: var TLoc) = else: if optNilCheck in p.options: linefmt(p, cpsStmts, "#chckNil((void*)$1);$n", addrLoc(p.config, loc)) - if loc.storage != OnStack: + if loc.storage != OnStack and containsGcRef: linefmt(p, cpsStmts, "#genericReset((void*)$1, $2);$n", addrLoc(p.config, loc), genTypeInfo(p.module, loc.t, loc.lode.info)) # XXX: generated reset procs should not touch the m_type @@ -1056,9 +1056,22 @@ proc genVarPrototype(m: BModule, n: PNode) = if sfVolatile in sym.flags: add(m.s[cfsVars], " volatile") addf(m.s[cfsVars], " $1;$n", [sym.loc.r]) +const + frameDefines = """ +$1 define nimfr_(proc, file) \ + TFrame FR_; \ + FR_.procname = proc; FR_.filename = file; FR_.line = 0; FR_.len = 0; #nimFrame(&FR_); + +$1 define nimfrs_(proc, file, slots, length) \ + struct {TFrame* prev;NCSTRING procname;NI line;NCSTRING filename; NI len; VarSlot s[slots];} FR_; \ + FR_.procname = proc; FR_.filename = file; FR_.line = 0; FR_.len = length; #nimFrame((TFrame*)&FR_); + +$1 define nimln_(n, file) \ + FR_.line = n; FR_.filename = file; +""" + proc addIntTypes(result: var Rope; conf: ConfigRef) {.inline.} = - addf(result, "#define NIM_NEW_MANGLING_RULES\L" & - "#define NIM_INTBITS $1\L", [ + addf(result, "#define NIM_INTBITS $1\L", [ platform.CPU[conf.target.targetCPU].intSize.rope]) if conf.cppCustomNamespace.len > 0: result.add("#define USE_NIM_NAMESPACE ") @@ -1302,6 +1315,7 @@ proc genInitCode(m: BModule) = ## this function is called in cgenWriteModules after all modules are closed, ## it means raising dependency on the symbols is too late as it will not propogate ## into other modules, only simple rope manipulations are allowed + appcg(m, m.s[cfsForwardTypes], frameDefines, [rope("#")]) var moduleInitRequired = false let initname = getInitName(m.module) @@ -1313,9 +1327,9 @@ proc genInitCode(m: BModule) = appcg(m, m.s[cfsTypeInit1], "static #TNimType $1[$2];$n", [m.nimTypesName, rope(m.nimTypes)]) - # Give this small function its own scope - addf(prc, "{$N", []) - block: + if m.preInitProc.s(cpsInit).len > 0 or m.preInitProc.s(cpsStmts).len > 0: + # Give this small function its own scope + addf(prc, "{$N", []) # Keep a bogus frame in case the code needs one add(prc, ~"\tTFrame FR_; FR_.len = 0;$N") @@ -1336,8 +1350,11 @@ proc genInitCode(m: BModule) = add(prc, genSectionStart(cpsStmts, m.config)) add(prc, m.preInitProc.s(cpsStmts)) add(prc, genSectionEnd(cpsStmts, m.config)) - addf(prc, "}$N", []) + addf(prc, "}$N", []) + # add new scope for following code, because old vcc compiler need variable + # be defined at the top of the block + addf(prc, "{$N", []) if m.initProc.gcFrameId > 0: moduleInitRequired = true add(prc, initGCFrame(m.initProc)) @@ -1374,6 +1391,7 @@ proc genInitCode(m: BModule) = if m.initProc.gcFrameId > 0: moduleInitRequired = true add(prc, deinitGCFrame(m.initProc)) + addf(prc, "}$N", []) addf(prc, "}$N$N", []) diff --git a/compiler/commands.nim b/compiler/commands.nim index 30521f9ca..af775f5cd 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -746,6 +746,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; else: conf.cppCustomNamespace = "Nim" defineSymbol(conf.symbols, "cppCompileToNamespace", conf.cppCustomNamespace) + of "docinternal": + processOnOffSwitchG(conf, {optDocInternal}, arg, pass, info) else: if strutils.find(switch, '.') >= 0: options.setConfigVar(conf, switch, arg) else: invalidCmdLineOption(conf, pass, switch, info) diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim index 03ce9a5cf..e21d532ea 100644 --- a/compiler/destroyer.nim +++ b/compiler/destroyer.nim @@ -132,61 +132,36 @@ type emptyNode: PNode otherRead: PNode - -proc isHarmlessVar*(s: PSym; c: Con): bool = - # 's' is harmless if it used only once and its - # definition/usage are not split by any labels: - # - # let s = foo() - # while true: - # a[i] = s - # - # produces: - # - # def s - # L1: - # use s - # goto L1 - # - # let s = foo() - # if cond: - # a[i] = s - # else: - # a[j] = s - # - # produces: - # - # def s - # fork L2 - # use s - # goto L3 - # L2: - # use s - # L3 - # - # So this analysis is for now overly conservative, but correct. - var defsite = -1 - var usages = 0 - for i in 0..<c.g.len: - case c.g[i].kind +proc isLastRead(s: PSym; c: var Con; pc, comesFrom: int): int = + var pc = pc + while pc < c.g.len: + case c.g[pc].kind of def: - if c.g[i].sym == s: - if defsite < 0: defsite = i - else: return false + if c.g[pc].sym == s: + # the path lead to a redefinition of 's' --> abandon it. + return high(int) + inc pc of use: - if c.g[i].sym == s: - if defsite < 0: return false - for j in defsite .. i: - # not within the same basic block? - if j in c.jumpTargets: return false - # if we want to die after the first 'use': - if usages > 1: return false - inc usages - #of useWithinCall: - # if c.g[i].sym == s: return false - of goto, fork: - discard "we do not perform an abstract interpretation yet" - result = usages <= 1 + if c.g[pc].sym == s: + c.otherRead = c.g[pc].n + return -1 + inc pc + of goto: + pc = pc + c.g[pc].dest + of fork: + # every branch must lead to the last read of the location: + var variantA = isLastRead(s, c, pc+1, pc) + if variantA < 0: return -1 + let variantB = isLastRead(s, c, pc + c.g[pc].dest, pc) + if variantB < 0: return -1 + elif variantA == high(int): + variantA = variantB + pc = variantA + of InstrKind.join: + let dest = pc + c.g[pc].dest + if dest == comesFrom: return pc + 1 + inc pc + return pc proc isLastRead(n: PNode; c: var Con): bool = # first we need to search for the instruction that belongs to 'n': @@ -195,59 +170,52 @@ proc isLastRead(n: PNode; c: var Con): bool = var instr = -1 for i in 0..<c.g.len: if c.g[i].n == n: - if instr < 0: instr = i - else: - # eh, we found two positions that belong to 'n'? - # better return 'false' then: - return false + if instr < 0: + instr = i + break + if instr < 0: return false # we go through all paths beginning from 'instr+1' and need to # ensure that we don't find another 'use X' instruction. if instr+1 >= c.g.len: return true - let s = n.sym - var pcs: seq[int] = @[instr+1] - var takenGotos: IntSet - var takenForks = initIntSet() - while pcs.len > 0: - var pc = pcs.pop - - takenGotos = initIntSet() - while pc < c.g.len: - case c.g[pc].kind - of def: - if c.g[pc].sym == s: - # the path lead to a redefinition of 's' --> abandon it. - when false: - # Too complex thinking ahead: In reality it is enough to find - # the 'def x' here on the current path to make the 'use x' valid. - # but for this the definition needs to dominate the usage: - var dominates = true - for j in pc+1 .. instr: - # not within the same basic block? - if c.g[j].kind in {goto, fork} and (j + c.g[j].dest) in (pc+1 .. instr): - #if j in c.jumpTargets: - dominates = false - if dominates: break - break - inc pc - of use: - if c.g[pc].sym == s: - c.otherRead = c.g[pc].n - return false - inc pc - of goto: - # we must leave endless loops eventually: - if not takenGotos.containsOrIncl(pc): - pc = pc + c.g[pc].dest - else: + when true: + result = isLastRead(n.sym, c, instr+1, -1) >= 0 + else: + let s = n.sym + var pcs: seq[int] = @[instr+1] + var takenGotos: IntSet + var takenForks = initIntSet() + while pcs.len > 0: + var pc = pcs.pop + + takenGotos = initIntSet() + while pc < c.g.len: + case c.g[pc].kind + of def: + if c.g[pc].sym == s: + # the path lead to a redefinition of 's' --> abandon it. + break + inc pc + of use: + if c.g[pc].sym == s: + c.otherRead = c.g[pc].n + return false + inc pc + of goto: + # we must leave endless loops eventually: + if not takenGotos.containsOrIncl(pc): + pc = pc + c.g[pc].dest + else: + inc pc + of fork: + # we follow the next instruction but push the dest onto our "work" stack: + if not takenForks.containsOrIncl(pc): + pcs.add pc + c.g[pc].dest + inc pc + of InstrKind.join: inc pc - of fork: - # we follow the next instruction but push the dest onto our "work" stack: - if not takenForks.containsOrIncl(pc): - pcs.add pc + c.g[pc].dest - inc pc - #echo c.graph.config $ n.info, " last read here!" - return true + #echo c.graph.config $ n.info, " last read here!" + return true template interestingSym(s: PSym): bool = s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ) diff --git a/compiler/dfa.nim b/compiler/dfa.nim index df9584576..462cf0fb7 100644 --- a/compiler/dfa.nim +++ b/compiler/dfa.nim @@ -34,12 +34,12 @@ import ast, astalgo, types, intsets, tables, msgs, options, lineinfos type InstrKind* = enum - goto, fork, def, use + goto, fork, join, def, use Instr* = object n*: PNode case kind*: InstrKind of def, use: sym*: PSym - of goto, fork: dest*: int + of goto, fork, join: dest*: int ControlFlowGraph* = seq[Instr] @@ -56,6 +56,7 @@ type inCall, inTryStmt: int blocks: seq[TBlock] tryStmtFixups: seq[TPosition] + forks: seq[TPosition] owner: PSym proc debugInfo(info: TLineInfo): string = @@ -67,18 +68,18 @@ proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) = var jumpTargets = initIntSet() let last = if last < 0: c.len-1 else: min(last, c.len-1) for i in start..last: - if c[i].kind in {goto, fork}: + if c[i].kind in {goto, fork, join}: jumpTargets.incl(i+c[i].dest) var i = start while i <= last: if i in jumpTargets: result.add("L" & $i & ":\n") result.add "\t" - result.add $c[i].kind + result.add ($i & " " & $c[i].kind) result.add "\t" case c[i].kind of def, use: result.add c[i].sym.name.s - of goto, fork: + of goto, fork, join: result.add "L" result.add c[i].dest+i result.add("\t#") @@ -98,11 +99,166 @@ proc echoCfg*(c: ControlFlowGraph; start=0; last = -1) {.deprecated.} = proc forkI(c: var Con; n: PNode): TPosition = result = TPosition(c.code.len) c.code.add Instr(n: n, kind: fork, dest: 0) + c.forks.add result proc gotoI(c: var Con; n: PNode): TPosition = result = TPosition(c.code.len) c.code.add Instr(n: n, kind: goto, dest: 0) +#[ + +Design of join +============== + +block: + if cond: break + def(x) + +use(x) + +Generates: + +L0: fork L1 + join L0 # patched. + goto Louter +L1: + def x + join L0 +Louter: + use x + + +block outer: + while a: + while b: + if foo: + if bar: + break outer # --> we need to 'join' every pushed 'fork' here + + +This works and then our abstract interpretation needs to deal with 'fork' +differently. It really causes a split in execution. Two threads are +"spawned" and both need to reach the 'join L' instruction. Afterwards +the abstract interpretations are joined and execution resumes single +threaded. + + +Abstract Interpretation +----------------------- + +proc interpret(pc, state, comesFrom): state = + result = state + # we need an explicit 'create' instruction (an explicit heap), in order + # to deal with 'var x = create(); var y = x; var z = y; destroy(z)' + while true: + case pc + of fork: + let a = interpret(pc+1, result, pc) + let b = interpret(forkTarget, result, pc) + result = a ++ b # ++ is a union operation + inc pc + of join: + if joinTarget == comesFrom: return result + else: inc pc + of use X: + if not result.contains(x): + error "variable not initialized " & x + inc pc + of def X: + if not result.contains(x): + result.incl X + else: + error "overwrite of variable causes memory leak " & x + inc pc + of destroy X: + result.excl X + +This is correct but still can lead to false positives: + +proc p(cond: bool) = + if cond: + new(x) + otherThings() + if cond: + destroy x + +Is not a leak. We should find a way to model *data* flow, not just +control flow. One solution is to rewrite the 'if' without a fork +instruction. The unstructured aspect can now be easily dealt with +the 'goto' and 'join' instructions. + +proc p(cond: bool) = + L0: fork Lend + new(x) + # do not 'join' here! + + Lend: + otherThings() + join L0 # SKIP THIS FOR new(x) SOMEHOW + destroy x + join L0 # but here. + + + +But if we follow 'goto Louter' we will never come to the join point. +We restore the bindings after popping pc from the stack then there +"no" problem?! + + +while cond: + prelude() + if not condB: break + postlude() + +---> +var setFlag = true +while cond and not setFlag: + prelude() + if not condB: + setFlag = true # BUT: Dependency + if not setFlag: # HERE + postlude() + +---> +var setFlag = true +while cond and not setFlag: + prelude() + if not condB: + postlude() + setFlag = true + + +------------------------------------------------- + +while cond: + prelude() + if more: + if not condB: break + stuffHere() + postlude() + +--> +var setFlag = true +while cond and not setFlag: + prelude() + if more: + if not condB: + setFlag = false + else: + stuffHere() + postlude() + else: + postlude() + +This is getting complicated. Instead we keep the whole 'join' idea but +duplicate the 'join' instructions on breaks and return exits! + +]# + +proc joinI(c: var Con; fromFork: TPosition; n: PNode) = + let dist = fromFork.int - c.code.len + c.code.add Instr(n: n, kind: join, dest: dist) + proc genLabel(c: Con): TPosition = result = TPosition(c.code.len) @@ -135,30 +291,97 @@ proc isTrue(n: PNode): bool = proc gen(c: var Con; n: PNode) # {.noSideEffect.} -proc genWhile(c: var Con; n: PNode) = - # L1: - # cond, tmp - # fork tmp, L2 - # body - # jmp L1 - # L2: - let L1 = c.genLabel - withBlock(nil): +when true: + proc genWhile(c: var Con; n: PNode) = + # We unroll every loop 3 times. We emulate 0, 1, 2 iterations + # through the loop. We need to prove this is correct for our + # purposes. But Herb Sutter claims it is. (Proof by authority.) + #[ + while cond: + body + + Becomes: + + if cond: + body + if cond: + body + if cond: + body + + We still need to ensure 'break' resolves properly, so an AST to AST + translation is impossible. + + So the code to generate is: + + cond + fork L4 # F1 + body + cond + fork L5 # F2 + body + cond + fork L6 # F3 + body + L6: + join F3 + L5: + join F2 + L4: + join F1 + ]# if isTrue(n.sons[0]): - c.gen(n.sons[1]) - c.jmpBack(n, L1) + # 'while true' is an idiom in Nim and so we produce + # better code for it: + for i in 0..2: + withBlock(nil): + c.gen(n.sons[1]) else: - c.gen(n.sons[0]) - let L2 = c.forkI(n) - c.gen(n.sons[1]) - c.jmpBack(n, L1) - c.patch(L2) + let oldForksLen = c.forks.len + var endings: array[3, TPosition] + for i in 0..2: + withBlock(nil): + c.gen(n.sons[0]) + endings[i] = c.forkI(n) + c.gen(n.sons[1]) + for i in countdown(endings.high, 0): + let endPos = endings[i] + c.patch(endPos) + c.joinI(c.forks.pop(), n) + doAssert(c.forks.len == oldForksLen) + +else: + + proc genWhile(c: var Con; n: PNode) = + # L1: + # cond, tmp + # fork tmp, L2 + # body + # jmp L1 + # L2: + let oldForksLen = c.forks.len + let L1 = c.genLabel + withBlock(nil): + if isTrue(n.sons[0]): + c.gen(n.sons[1]) + c.jmpBack(n, L1) + else: + c.gen(n.sons[0]) + let L2 = c.forkI(n) + c.gen(n.sons[1]) + c.jmpBack(n, L1) + c.patch(L2) + setLen(c.forks, oldForksLen) proc genBlock(c: var Con; n: PNode) = withBlock(n.sons[0].sym): c.gen(n.sons[1]) +proc genJoins(c: var Con; n: PNode) = + for i in countdown(c.forks.high, 0): joinI(c, c.forks[i], n) + proc genBreak(c: var Con; n: PNode) = + genJoins(c, n) let L1 = c.gotoI(n) if n.sons[0].kind == nkSym: #echo cast[int](n.sons[0].sym) @@ -170,28 +393,76 @@ proc genBreak(c: var Con; n: PNode) = else: c.blocks[c.blocks.high].fixups.add L1 +template forkT(n, body) = + let oldLen = c.forks.len + let L1 = c.forkI(n) + body + c.patch(L1) + c.joinI(L1, n) + setLen(c.forks, oldLen) + proc genIf(c: var Con, n: PNode) = + #[ + + if cond: + A + elif condB: + B + elif condC: + C + else: + D + + cond + fork L1 + A + goto Lend + L1: + condB + fork L2 + B + goto Lend2 + L2: + condC + fork L3 + C + goto Lend3 + L3: + D + goto Lend3 # not eliminated to simplify the join generation + Lend3: + join F3 + Lend2: + join F2 + Lend: + join F1 + + ]# + let oldLen = c.forks.len var endings: seq[TPosition] = @[] for i in countup(0, len(n) - 1): var it = n.sons[i] c.gen(it.sons[0]) if it.len == 2: - let elsePos = c.forkI(it.sons[1]) + let elsePos = forkI(c, it[1]) c.gen(it.sons[1]) - if i < sonsLen(n)-1: - endings.add(c.gotoI(it.sons[1])) + endings.add(c.gotoI(it.sons[1])) c.patch(elsePos) - for endPos in endings: c.patch(endPos) + for i in countdown(endings.high, 0): + let endPos = endings[i] + c.patch(endPos) + c.joinI(c.forks.pop(), n) + doAssert(c.forks.len == oldLen) proc genAndOr(c: var Con; n: PNode) = # asgn dest, a # fork L1 # asgn dest, b # L1: + # join F1 c.gen(n.sons[1]) - let L1 = c.forkI(n) - c.gen(n.sons[2]) - c.patch(L1) + forkT(n): + c.gen(n.sons[2]) proc genCase(c: var Con; n: PNode) = # if (!expr1) goto L1; @@ -204,72 +475,94 @@ proc genCase(c: var Con; n: PNode) = # L2: # elsePart # Lend: - when false: - # XXX Exhaustiveness is not yet mapped to the control flow graph as - # it seems to offer no benefits for the 'last read of' question. - let isExhaustive = skipTypes(n.sons[0].typ, - abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString} or - lastSon(n).kind == nkElse + let isExhaustive = skipTypes(n.sons[0].typ, + abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString} var endings: seq[TPosition] = @[] + let oldLen = c.forks.len c.gen(n.sons[0]) for i in 1 ..< n.len: let it = n.sons[i] if it.len == 1: c.gen(it.sons[0]) + elif i == n.len-1 and isExhaustive: + # treat the last branch as 'else' if this is an exhaustive case statement. + c.gen(it.lastSon) else: let elsePos = c.forkI(it.lastSon) c.gen(it.lastSon) - if i < sonsLen(n)-1: - endings.add(c.gotoI(it.lastSon)) + endings.add(c.gotoI(it.lastSon)) c.patch(elsePos) - for endPos in endings: c.patch(endPos) + for i in countdown(endings.high, 0): + let endPos = endings[i] + c.patch(endPos) + c.joinI(c.forks.pop(), n) + doAssert(c.forks.len == oldLen) proc genTry(c: var Con; n: PNode) = + let oldLen = c.forks.len var endings: seq[TPosition] = @[] inc c.inTryStmt - var newFixups: seq[TPosition] - swap(newFixups, c.tryStmtFixups) + let oldFixups = c.tryStmtFixups.len - let elsePos = c.forkI(n) + #let elsePos = c.forkI(n) c.gen(n.sons[0]) dec c.inTryStmt - for f in newFixups: + for i in oldFixups..c.tryStmtFixups.high: + let f = c.tryStmtFixups[i] c.patch(f) - swap(newFixups, c.tryStmtFixups) + # we also need to produce join instructions + # for the 'fork' that might preceed the goto instruction + if f.int-1 >= 0 and c.code[f.int-1].kind == fork: + c.joinI(TPosition(f.int-1), n) + + setLen(c.tryStmtFixups, oldFixups) - c.patch(elsePos) + #c.patch(elsePos) for i in 1 ..< n.len: let it = n.sons[i] if it.kind != nkFinally: var blen = len(it) let endExcept = c.forkI(it) c.gen(it.lastSon) - if i < sonsLen(n)-1: - endings.add(c.gotoI(it)) + endings.add(c.gotoI(it)) c.patch(endExcept) - for endPos in endings: c.patch(endPos) + for i in countdown(endings.high, 0): + let endPos = endings[i] + c.patch(endPos) + c.joinI(c.forks.pop(), n) + + # join the 'elsePos' forkI instruction: + #c.joinI(c.forks.pop(), n) + let fin = lastSon(n) if fin.kind == nkFinally: c.gen(fin.sons[0]) + doAssert(c.forks.len == oldLen) + +template genNoReturn(c: var Con; n: PNode) = + # leave the graph + c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len) proc genRaise(c: var Con; n: PNode) = + genJoins(c, n) gen(c, n.sons[0]) if c.inTryStmt > 0: c.tryStmtFixups.add c.gotoI(n) else: - c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len) + genNoReturn(c, n) proc genImplicitReturn(c: var Con) = if c.owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter} and resultPos < c.owner.ast.len: gen(c, c.owner.ast.sons[resultPos]) proc genReturn(c: var Con; n: PNode) = + genJoins(c, n) if n.sons[0].kind != nkEmpty: gen(c, n.sons[0]) else: genImplicitReturn(c) - c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len) + genNoReturn(c, n) const InterestingSyms = {skVar, skResult, skLet, skParam} @@ -287,6 +580,14 @@ proc genDef(c: var Con; n: PNode) = if n.kind == nkSym and n.sym.kind in InterestingSyms: c.code.add Instr(n: n, kind: def, sym: n.sym) +proc canRaise(fn: PNode): bool = + const magicsThatCanRaise = { + mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst} + if fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise: + result = false + else: + result = true + proc genCall(c: var Con; n: PNode) = gen(c, n[0]) var t = n[0].typ @@ -297,8 +598,16 @@ proc genCall(c: var Con; n: PNode) = if t != nil and i < t.len and t.sons[i].kind == tyVar: genDef(c, n[i]) # every call can potentially raise: - if c.inTryStmt > 0: - c.tryStmtFixups.add c.forkI(n) + if c.inTryStmt > 0 and canRaise(n[0]): + # we generate the instruction sequence: + # fork L1 + # goto exceptionHandler (except or finally) + # L1: + # join F1 + let endGoto = c.forkI(n) + c.tryStmtFixups.add c.gotoI(n) + c.patch(endGoto) + c.joinI(c.forks.pop(), n) dec c.inCall proc genMagic(c: var Con; n: PNode; m: TMagic) = @@ -307,9 +616,6 @@ proc genMagic(c: var Con; n: PNode; m: TMagic) = of mNew, mNewFinalize: genDef(c, n[1]) for i in 2..<n.len: gen(c, n[i]) - of mExit: - genCall(c, n) - c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len) else: genCall(c, n) @@ -334,6 +640,8 @@ proc gen(c: var Con; n: PNode) = genMagic(c, n, s.magic) else: genCall(c, n) + if sfNoReturn in n.sons[0].sym.flags: + genNoReturn(c, n) else: genCall(c, n) of nkCharLit..nkNilLit: discard @@ -368,114 +676,48 @@ proc gen(c: var Con; n: PNode) = doAssert false, "dfa construction pass requires the elimination of 'defer'" else: discard -proc dfa(code: seq[Instr]; conf: ConfigRef) = - var u = newSeq[IntSet](code.len) # usages - var d = newSeq[IntSet](code.len) # defs - var c = newSeq[IntSet](code.len) # consumed - var backrefs = initTable[int, int]() - for i in 0..<code.len: - u[i] = initIntSet() - d[i] = initIntSet() - c[i] = initIntSet() - case code[i].kind - of use: u[i].incl(code[i].sym.id) - of def: d[i].incl(code[i].sym.id) - of fork, goto: - let d = i+code[i].dest - backrefs.add(d, i) - - var w = @[0] - var maxIters = 50 - var someChange = true - var takenGotos = initIntSet() - var consuming = -1 - while w.len > 0 and maxIters > 0: # and someChange: - dec maxIters - var pc = w.pop() # w[^1] - var prevPc = -1 - # this simulates a single linear control flow execution: - while pc < code.len: - if prevPc >= 0: - someChange = false - # merge step and test for changes (we compute the fixpoints here): - # 'u' needs to be the union of prevPc, pc - # 'd' needs to be the intersection of 'pc' - for id in u[prevPc]: - if not u[pc].containsOrIncl(id): - someChange = true - # in (a; b) if ``a`` sets ``v`` so does ``b``. The intersection - # is only interesting on merge points: - for id in d[prevPc]: - if not d[pc].containsOrIncl(id): - someChange = true - # if this is a merge point, we take the intersection of the 'd' sets: - if backrefs.hasKey(pc): - var intersect = initIntSet() - assign(intersect, d[pc]) - var first = true - for prevPc in backrefs.allValues(pc): - for def in d[pc]: - if def notin d[prevPc]: - excl(intersect, def) - someChange = true - when defined(debugDfa): - echo "Excluding ", pc, " prev ", prevPc - assign d[pc], intersect - if consuming >= 0: - if not c[pc].containsOrIncl(consuming): - someChange = true - consuming = -1 - - # our interpretation ![I!]: - prevPc = pc - case code[pc].kind - of goto: - # we must leave endless loops eventually: - if not takenGotos.containsOrIncl(pc) or someChange: - pc = pc + code[pc].dest - else: - inc pc - of fork: - # we follow the next instruction but push the dest onto our "work" stack: - #if someChange: - w.add pc + code[pc].dest - inc pc - of use: - #if not d[prevPc].missingOrExcl(): - # someChange = true - consuming = code[pc].sym.id - inc pc - of def: - if not d[pc].containsOrIncl(code[pc].sym.id): - someChange = true - inc pc - - when defined(useDfa) and defined(debugDfa): - for i in 0..<code.len: - echo "PC ", i, ": defs: ", d[i], "; uses ", u[i], "; consumes ", c[i] - - # now check the condition we're interested in: - for i in 0..<code.len: - case code[i].kind - of use: - let s = code[i].sym - if s.id notin d[i]: - localError(conf, code[i].n.info, "usage of uninitialized variable: " & s.name.s) - if s.id in c[i]: - localError(conf, code[i].n.info, "usage of an already consumed variable: " & s.name.s) - - else: discard - -proc dataflowAnalysis*(s: PSym; body: PNode; conf: ConfigRef) = - var c = Con(code: @[], blocks: @[]) - gen(c, body) - genImplicitReturn(c) - when defined(useDfa) and defined(debugDfa): echoCfg(c.code) - dfa(c.code, conf) - proc constructCfg*(s: PSym; body: PNode): ControlFlowGraph = ## constructs a control flow graph for ``body``. var c = Con(code: @[], blocks: @[], owner: s) gen(c, body) genImplicitReturn(c) shallowCopy(result, c.code) + +proc interpret(code: ControlFlowGraph; pc: int, state: seq[PSym], comesFrom: int; threadId: int): (seq[PSym], int) = + var res = state + var pc = pc + while pc < code.len: + #echo threadId, " ", code[pc].kind + case code[pc].kind + of goto: + pc = pc + code[pc].dest + of fork: + let target = pc + code[pc].dest + let (branchA, pcA) = interpret(code, pc+1, res, pc, threadId+1) + let (branchB, _) = interpret(code, target, res, pc, threadId+2) + # we add vars if they are in both branches: + for v in branchB: + if v in branchA: + if v notin res: + res.add v + pc = pcA+1 + of join: + let target = pc + code[pc].dest + if comesFrom == target: return (res, pc) + inc pc + of use: + let v = code[pc].sym + if v notin res and v.kind != skParam: + echo "attempt to read uninitialized variable ", v.name.s + inc pc + of def: + let v = code[pc].sym + if v notin res: + res.add v + inc pc + return (res, pc) + +proc dataflowAnalysis*(s: PSym; body: PNode) = + let c = constructCfg(s, body) + #echoCfg c + discard interpret(c, 0, @[], -1, 1) diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 33cd98f38..a7f7d77b5 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -144,28 +144,28 @@ proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef, initStrTable result.types result.onTestSnippet = proc (gen: var RstGenerator; filename, cmd: string; status: int; content: string) = - var d = TDocumentor(gen) - var outp: AbsoluteFile - if filename.len == 0: - inc(d.id) - let nameOnly = splitFile(d.filename).name - let subdir = getNimcacheDir(conf) / RelativeDir(nameOnly) - createDir(subdir) - outp = subdir / RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim") - elif isAbsolute(filename): - outp = AbsoluteFile filename - else: - # Nim's convention: every path is relative to the file it was written in: - outp = splitFile(d.filename).dir.AbsoluteDir / RelativeFile(filename) - # Include the current file if we're parsing a nim file - let importStmt = if d.isPureRst: "" else: "import \"$1\"\n" % [d.filename] - writeFile(outp, importStmt & content) - let c = if cmd.startsWith("nim "): os.getAppFilename() & cmd.substr(3) - else: cmd - let c2 = c % quoteShell(outp) - rawMessage(conf, hintExecuting, c2) - if execShellCmd(c2) != status: - rawMessage(conf, errGenerated, "executing of external program failed: " & c2) + var d = TDocumentor(gen) + var outp: AbsoluteFile + if filename.len == 0: + inc(d.id) + let nameOnly = splitFile(d.filename).name + let subdir = getNimcacheDir(conf) / RelativeDir(nameOnly) + createDir(subdir) + outp = subdir / RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim") + elif isAbsolute(filename): + outp = AbsoluteFile filename + else: + # Nim's convention: every path is relative to the file it was written in: + outp = splitFile(d.filename).dir.AbsoluteDir / RelativeFile(filename) + # Include the current file if we're parsing a nim file + let importStmt = if d.isPureRst: "" else: "import \"$1\"\n" % [d.filename] + writeFile(outp, importStmt & content) + let c = if cmd.startsWith("nim "): os.getAppFilename() & cmd.substr(3) + else: cmd + let c2 = c % quoteShell(outp) + rawMessage(conf, hintExecuting, c2) + if execShellCmd(c2) != status: + rawMessage(conf, errGenerated, "executing of external program failed: " & c2) result.emitted = initIntSet() result.destFile = getOutFile2(conf, relativeTo(filename, conf.projectPath), outExt, RelativeDir"htmldocs", false) @@ -300,13 +300,17 @@ proc externalDep(d: PDoc; module: PSym): string = else: result = extractFilename toFullPath(d.conf, FileIndex module.position) -proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRenderFlags = {}) = +proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRenderFlags = {}; + procLink: Rope) = var r: TSrcGen var literal = "" initTokRender(r, n, renderFlags) var kind = tkEof + var tokenPos = 0 + var procTokenPos = 0 while true: getNextTok(r, kind, literal) + inc tokenPos case kind of tkEof: break @@ -314,6 +318,8 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe dispA(d.conf, result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}", [rope(esc(d.target, literal))]) of tokKeywordLow..tokKeywordHigh: + if kind in {tkProc, tkMethod, tkIterator, tkMacro, tkTemplate, tkFunc, tkConverter}: + procTokenPos = tokenPos dispA(d.conf, result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}", [rope(literal)]) of tkOpr: @@ -333,7 +339,11 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe "\\spanFloatNumber{$1}", [rope(esc(d.target, literal))]) of tkSymbol: let s = getTokSym(r) - if s != nil and s.kind == skType and sfExported in s.flags and + # -2 because of the whitespace in between: + if procTokenPos == tokenPos-2 and procLink != nil: + dispA(d.conf, result, "<a href=\"#$2\"><span class=\"Identifier\">$1</span></a>", + "\\spanIdentifier{$1}", [rope(esc(d.target, literal)), procLink]) + elif s != nil and s.kind == skType and sfExported in s.flags and s.owner != nil and belongsToPackage(d.conf, s.owner) and d.target == outHtml: let external = externalDep(d, s.owner) @@ -445,7 +455,7 @@ proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope) = for b in body: if i > 0: dest.add "\n" inc i - nodeToHighlightedHtml(d, b, dest, {}) + nodeToHighlightedHtml(d, b, dest, {}, nil) dest.add(d.config.getOrDefault"doc.listing_end" % id) else: discard for i in 0 ..< n.safeLen: @@ -464,7 +474,10 @@ proc isVisible(d: PDoc; n: PNode): bool = # we cannot generate code for forwarded symbols here as we have no # exception tracking information here. Instead we copy over the comment # from the proc header. - result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported} + if optDocInternal in d.conf.globalOptions: + result = {sfFromGeneric, sfForward}*n.sym.flags == {} + else: + result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported} if result and containsOrIncl(d.emitted, n.sym.id): result = false elif n.kind == nkPragmaExpr: @@ -545,16 +558,19 @@ proc complexName(k: TSymKind, n: PNode, baseName: string): string = ## If you modify the output of this proc, please update the anchor generation ## section of ``doc/docgen.txt``. result = baseName - case k: - of skProc, skFunc: result.add(defaultParamSeparator) - of skMacro: result.add(".m" & defaultParamSeparator) - of skMethod: result.add(".e" & defaultParamSeparator) - of skIterator: result.add(".i" & defaultParamSeparator) - of skTemplate: result.add(".t" & defaultParamSeparator) - of skConverter: result.add(".c" & defaultParamSeparator) + case k + of skProc, skFunc: discard + of skMacro: result.add(".m") + of skMethod: result.add(".e") + of skIterator: result.add(".i") + of skTemplate: result.add(".t") + of skConverter: result.add(".c") else: discard if len(n) > paramsPos and n[paramsPos].kind == nkFormalParams: - result.add(renderParamTypes(n[paramsPos])) + let params = renderParamTypes(n[paramsPos]) + if params.len > 0: + result.add(defaultParamSeparator) + result.add(params) proc isCallable(n: PNode): bool = ## Returns true if `n` contains a callable node. @@ -612,9 +628,6 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = break plainName.add(literal) - nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments, - renderDocComments, renderSyms}) - inc(d.id) let plainNameRope = rope(xmltree.escape(plainName.strip)) @@ -627,6 +640,9 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = symbolOrIdRope = symbolOrId.rope symbolOrIdEncRope = encodeUrl(symbolOrId).rope + nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments, + renderDocComments, renderSyms}, symbolOrIdEncRope) + var seeSrcRope: Rope = nil let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc") if docItemSeeSrc.len > 0: diff --git a/compiler/gorgeimpl.nim b/compiler/gorgeimpl.nim index 44636f382..534ef9fdc 100644 --- a/compiler/gorgeimpl.nim +++ b/compiler/gorgeimpl.nim @@ -24,11 +24,11 @@ proc readOutput(p: Process): (string, int) = proc opGorge*(cmd, input, cache: string, info: TLineInfo; conf: ConfigRef): (string, int) = let workingDir = parentDir(toFullPath(conf, info)) - if cache.len > 0:# and optForceFullMake notin gGlobalOptions: + if cache.len > 0: let h = secureHash(cmd & "\t" & input & "\t" & cache) let filename = toGeneratedFile(conf, AbsoluteFile("gorge_" & $h), "txt").string var f: File - if open(f, filename): + if optForceFullMake notin conf.globalOptions and open(f, filename): result = (f.readAll, 0) f.close return diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 0dd5820b4..6bb2c3fa3 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -203,7 +203,7 @@ proc toMsgFilename*(conf: ConfigRef; info: TLineInfo): string = result = absPath else: let relPath = conf.m.fileInfos[info.fileIndex.int32].projPath.string - result = if absPath.len < relPath.len: absPath else: relPath + result = if relPath.count("..") > 2: absPath else: relPath proc toLinenumber*(info: TLineInfo): int {.inline.} = result = int info.line @@ -432,7 +432,7 @@ proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) = proc sourceLine*(conf: ConfigRef; i: TLineInfo): string = if i.fileIndex.int32 < 0: return "" - if not optPreserveOrigSource(conf) and conf.m.fileInfos[i.fileIndex.int32].lines.len == 0: + if conf.m.fileInfos[i.fileIndex.int32].lines.len == 0: try: for line in lines(toFullPath(conf, i)): addSourceLine conf, i.fileIndex, line.string diff --git a/compiler/options.nim b/compiler/options.nim index d39b0a268..54276f99d 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -77,6 +77,7 @@ type # please make sure we have under 32 options optExcessiveStackTrace # fully qualified module filenames optShowAllMismatches # show all overloading resolution candidates optWholeProject # for 'doc2': output any dependency + optDocInternal # generate documentation for non-exported symbols optMixedMode # true if some module triggered C++ codegen optListFullPaths # use full paths in toMsgFilename, toFilename optNoNimblePath diff --git a/compiler/sem.nim b/compiler/sem.nim index 3763c9b84..0a3b60ab3 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -450,17 +450,18 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, flags: TExprFlags = {}): PNode = pushInfoContext(c.config, nOrig.info, sym.detailedInfo) - markUsed(c.config, n.info, sym, c.graph.usageSym) - onUse(n.info, sym) + let info = getCallLineInfo(n) + markUsed(c.config, info, sym, c.graph.usageSym) + onUse(info, sym) if sym == c.p.owner: - globalError(c.config, n.info, "recursive dependency: '$1'" % sym.name.s) + globalError(c.config, info, "recursive dependency: '$1'" % sym.name.s) let genericParams = if sfImmediate in sym.flags: 0 else: sym.ast[genericParamsPos].len let suppliedParams = max(n.safeLen - 1, 0) if suppliedParams < genericParams: - globalError(c.config, n.info, errMissingGenericParamsForTemplate % n.renderTree) + globalError(c.config, info, errMissingGenericParamsForTemplate % n.renderTree) #if c.evalContext == nil: # c.evalContext = c.createEvalContext(emStatic) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 3723d3fc1..e8723e3df 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -462,16 +462,23 @@ proc updateDefaultParams(call: PNode) = if nfDefaultRefsParam in def.flags: call.flags.incl nfDefaultRefsParam call[i] = def +proc getCallLineInfo(n: PNode): TLineInfo = + case n.kind + of nkAccQuoted, nkBracketExpr, nkCall, nkCommand: getCallLineInfo(n.sons[0]) + of nkDotExpr: getCallLineInfo(n.sons[1]) + else: n.info + proc semResolvedCall(c: PContext, x: TCandidate, n: PNode, flags: TExprFlags): PNode = assert x.state == csMatch var finalCallee = x.calleeSym - markUsed(c.config, n.sons[0].info, finalCallee, c.graph.usageSym) - onUse(n.sons[0].info, finalCallee) + let info = getCallLineInfo(n) + markUsed(c.config, info, finalCallee, c.graph.usageSym) + onUse(info, finalCallee) assert finalCallee.ast != nil if x.hasFauxMatch: result = x.call - result.sons[0] = newSymNode(finalCallee, result.sons[0].info) + result.sons[0] = newSymNode(finalCallee, getCallLineInfo(result.sons[0])) if containsGenericType(result.typ) or x.fauxMatch == tyUnknown: result.typ = newTypeS(x.fauxMatch, c) return @@ -496,7 +503,7 @@ proc semResolvedCall(c: PContext, x: TCandidate, result = x.call instGenericConvertersSons(c, result, x) - result[0] = newSymNode(finalCallee, result[0].info) + result[0] = newSymNode(finalCallee, getCallLineInfo(result[0])) result.typ = finalCallee.typ.sons[0] updateDefaultParams(result) @@ -551,7 +558,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, notFoundError(c, n, errors) proc explicitGenericInstError(c: PContext; n: PNode): PNode = - localError(c.config, n.info, errCannotInstantiateX % renderTree(n)) + localError(c.config, getCallLineInfo(n), errCannotInstantiateX % renderTree(n)) result = n proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode = @@ -574,9 +581,10 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode = if tm in {isNone, isConvertible}: return nil var newInst = generateInstance(c, s, m.bindings, n.info) newInst.typ.flags.excl tfUnresolved - markUsed(c.config, n.info, s, c.graph.usageSym) - onUse(n.info, s) - result = newSymNode(newInst, n.info) + let info = getCallLineInfo(n) + markUsed(c.config, info, s, c.graph.usageSym) + onUse(info, s) + result = newSymNode(newInst, info) proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = assert n.kind == nkBracketExpr @@ -593,7 +601,7 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = # number of generic type parameters: if safeLen(s.ast.sons[genericParamsPos]) != n.len-1: let expected = safeLen(s.ast.sons[genericParamsPos]) - localError(c.config, n.info, errGenerated, "cannot instantiate: '" & renderTree(n) & + localError(c.config, getCallLineInfo(n), errGenerated, "cannot instantiate: '" & renderTree(n) & "'; got " & $(n.len-1) & " type(s) but expected " & $expected) return n result = explicitGenericSym(c, n, s) @@ -602,7 +610,7 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = # choose the generic proc with the proper number of type parameters. # XXX I think this could be improved by reusing sigmatch.paramTypesMatch. # It's good enough for now. - result = newNodeI(a.kind, n.info) + result = newNodeI(a.kind, getCallLineInfo(n)) for i in countup(0, len(a)-1): var candidate = a.sons[i].sym if candidate.kind in {skProc, skMethod, skConverter, diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 82f948492..68f1c6c3a 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -24,15 +24,18 @@ const proc semTemplateExpr(c: PContext, n: PNode, s: PSym, flags: TExprFlags = {}): PNode = - markUsed(c.config, n.info, s, c.graph.usageSym) - onUse(n.info, s) + let info = getCallLineInfo(n) + markUsed(c.config, info, s, c.graph.usageSym) + onUse(info, s) + # Note: This is n.info on purpose. It prevents template from creating an info + # context when called from an another template pushInfoContext(c.config, n.info, s.detailedInfo) result = evalTemplate(n, s, getCurrOwner(c), c.config, efFromHlo in flags) if efNoSemCheck notin flags: result = semAfterMacroCall(c, n, result, s, flags) popInfoContext(c.config) # XXX: A more elaborate line info rewrite might be needed - result.info = n.info + result.info = info proc semFieldAccess(c: PContext, n: PNode, flags: TExprFlags = {}): PNode @@ -1084,8 +1087,9 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or (n.kind notin nkCallKinds and s.requiredParams > 0) or sfCustomPragma in sym.flags: - markUsed(c.config, n.info, s, c.graph.usageSym) - onUse(n.info, s) + let info = getCallLineInfo(n) + markUsed(c.config, info, s, c.graph.usageSym) + onUse(info, s) result = symChoice(c, n, s, scClosed) else: result = semTemplateExpr(c, n, s, flags) @@ -1171,9 +1175,10 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = onUse(n.info, s) result = newSymNode(s, n.info) else: - markUsed(c.config, n.info, s, c.graph.usageSym) - onUse(n.info, s) - result = newSymNode(s, n.info) + let info = getCallLineInfo(n) + markUsed(c.config, info, s, c.graph.usageSym) + onUse(info, s) + result = newSymNode(s, info) proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = ## returns nil if it's not a built-in field access @@ -1286,7 +1291,11 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = if ty.sons[0] == nil: break ty = skipTypes(ty.sons[0], skipPtrs) if f != nil: - if fieldVisible(c, f): + let visibilityCheckNeeded = + if n[1].kind == nkSym and n[1].sym == f: + false # field lookup was done already, likely by hygienic template or bindSym + else: true + if not visibilityCheckNeeded or fieldVisible(c, f): # is the access to a public field or in the same module or in a friend? markUsed(c.config, n.sons[1].info, f, c.graph.usageSym) onUse(n.sons[1].info, f) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 05c8b181c..2311136b4 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -410,4 +410,9 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode, result = n else: result = plugin(c, n) + of mNewFinalize: + # Make sure the finalizer procedure refers to a procedure + if n[^1].kind == nkSym and n[^1].sym.kind notin {skProc, skFunc}: + localError(c.config, n.info, "finalizer must be a direct reference to a procedure") + result = n else: result = n diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 75dea069f..622e72074 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -353,6 +353,8 @@ proc trackTryStmt(tracked: PEffects, n: PNode) = var branches = 1 var hasFinally = false + + # Collect the exceptions caught by the except branches for i in 1 ..< n.len: let b = n.sons[i] let blen = sonsLen(b) @@ -368,12 +370,18 @@ proc trackTryStmt(tracked: PEffects, n: PNode) = else: assert(b.sons[j].kind == nkType) catches(tracked, b.sons[j].typ) + else: + assert b.kind == nkFinally + # Add any other exception raised in the except bodies + for i in 1 ..< n.len: + let b = n.sons[i] + let blen = sonsLen(b) + if b.kind == nkExceptBranch: setLen(tracked.init, oldState) track(tracked, b.sons[blen-1]) for i in oldState..<tracked.init.len: addToIntersection(inter, tracked.init[i]) else: - assert b.kind == nkFinally setLen(tracked.init, oldState) track(tracked, b.sons[blen-1]) hasFinally = true @@ -1013,7 +1021,7 @@ proc trackProc*(g: ModuleGraph; s: PSym, body: PNode) = "declared lock level is $1, but real lock level is $2" % [$s.typ.lockLevel, $t.maxLockLevel]) when defined(useDfa): - if s.kind == skFunc: + if s.name.s == "testp": dataflowAnalysis(s, body) when false: trackWrites(s, body) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 288820d86..5e9d5d9c5 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -279,7 +279,8 @@ proc semTry(c: PContext, n: PNode; flags: TExprFlags): PNode = for i in 1..last: var it = n.sons[i] let j = it.len-1 - it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) + if not endsInNoReturn(it.sons[j]): + it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) result.typ = typ proc fitRemoveHiddenConv(c: PContext, typ: PType, n: PNode): PNode = @@ -432,7 +433,6 @@ proc setVarType(c: PContext; v: PSym, typ: PType) = proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = var b: PNode result = copyNode(n) - var hasCompileTime = false for i in countup(0, sonsLen(n)-1): var a = n.sons[i] if c.config.cmd == cmdIdeTools: suggestStmt(c, a) @@ -440,11 +440,11 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config) checkMinSonsLen(a, 3, c.config) var length = sonsLen(a) - var typ: PType + + var typ: PType = nil if a.sons[length-2].kind != nkEmpty: typ = semTypeNode(c, a.sons[length-2], nil) - else: - typ = nil + var def: PNode = c.graph.emptyNode if a.sons[length-1].kind != nkEmpty: def = semExprWithType(c, a.sons[length-1], {efAllowDestructor}) @@ -556,13 +556,12 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = else: v.typ = tup b.sons[j] = newSymNode(v) checkNilable(c, v) - if sfCompileTime in v.flags: hasCompileTime = true + if sfCompileTime in v.flags: + var x = newNodeI(result.kind, v.info) + addSon(x, result[i]) + vm.setupCompileTimeVar(c.module, c.graph, x) if v.flags * {sfGlobal, sfThread} == {sfGlobal}: message(c.config, v.info, hintGlobalVar) - if hasCompileTime: - vm.setupCompileTimeVar(c.module, c.graph, result) - # handled by the VM codegen: - #c.graph.recordStmt(c.graph, c.module, result) proc semConst(c: PContext, n: PNode): PNode = result = copyNode(n) @@ -583,9 +582,19 @@ proc semConst(c: PContext, n: PNode): PNode = if def == nil: localError(c.config, a.sons[length-1].info, errConstExprExpected) continue + + if def.typ.kind == tyTypeDesc and c.p.owner.kind != skMacro: + # prevent the all too common 'const x = int' bug: + localError(c.config, def.info, "'typedesc' metatype is not valid here; typed '=' instead of ':'?") + def.typ = errorType(c) + # check type compatibility between def.typ and typ: if typ != nil: - def = fitRemoveHiddenConv(c, typ, def) + if typ.isMetaType: + def = inferWithMetatype(c, typ, def) + typ = def.typ + else: + def = fitRemoveHiddenConv(c, typ, def) else: typ = def.typ if typ == nil: diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 14507cf9d..d920a54b8 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -58,25 +58,26 @@ proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule): PNode = inc(i) if i > 1: break a = nextOverloadIter(o, c, n) + let info = getCallLineInfo(n) if i <= 1 and r != scForceOpen: # XXX this makes more sense but breaks bootstrapping for now: # (s.kind notin routineKinds or s.magic != mNone): # for instance 'nextTry' is both in tables.nim and astalgo.nim ... - result = newSymNode(s, n.info) - markUsed(c.config, n.info, s, c.graph.usageSym) - onUse(n.info, s) + result = newSymNode(s, info) + markUsed(c.config, info, s, c.graph.usageSym) + onUse(info, s) else: # semantic checking requires a type; ``fitNode`` deals with it # appropriately let kind = if r == scClosed or n.kind == nkDotExpr: nkClosedSymChoice else: nkOpenSymChoice - result = newNodeIT(kind, n.info, newTypeS(tyNone, c)) + result = newNodeIT(kind, info, newTypeS(tyNone, c)) a = initOverloadIter(o, c, n) while a != nil: if a.kind != skModule: incl(a.flags, sfUsed) - addSon(result, newSymNode(a, n.info)) - onUse(n.info, a) + addSon(result, newSymNode(a, info)) + onUse(info, a) a = nextOverloadIter(o, c, n) proc semBindStmt(c: PContext, n: PNode, toBind: var IntSet): PNode = diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index c28902b1f..fbf363834 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1015,8 +1015,8 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, result = addImplicitGeneric(copyType(paramType, getCurrOwner(c), false)) of tyGenericParam: - markUsed(c.config, info, paramType.sym, c.graph.usageSym) - onUse(info, paramType.sym) + markUsed(c.config, paramType.sym.info, paramType.sym, c.graph.usageSym) + onUse(paramType.sym.info, paramType.sym) if tfWildcard in paramType.flags: paramType.flags.excl tfWildcard paramType.sym.kind = skType @@ -1474,7 +1474,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = if c.config.cmd == cmdIdeTools: suggestExpr(c, n) case n.kind - of nkEmpty: discard + of nkEmpty: result = n.typ of nkTypeOfExpr: # for ``type(countup(1,3))``, see ``tests/ttoseq``. checkSonsLen(n, 1, c.config) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index f3c12e557..027ffd4aa 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -490,7 +490,12 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = result.kind = tyUserTypeClassInst of tyGenericBody: - localError(cl.c.config, cl.info, "cannot instantiate: '" & typeToString(t) & "'") + localError( + cl.c.config, + cl.info, + "cannot instantiate: '" & + typeToString(t, preferDesc) & + "'; Maybe generic arguments are missing?") result = errorType(cl.c) #result = replaceTypeVarsT(cl, lastSon(t)) @@ -555,6 +560,14 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = for i in countup(0, sonsLen(result) - 1): if result.sons[i] != nil: + if result.sons[i].kind == tyGenericBody: + localError( + cl.c.config, + t.sym.info, + "cannot instantiate '" & + typeToString(result.sons[i], preferDesc) & + "' inside of type definition: '" & + t.owner.name.s & "'; Maybe generic arguments are missing?") var r = replaceTypeVarsT(cl, result.sons[i]) if result.kind == tyObject: # carefully coded to not skip the precious tyGenericInst: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index e08559db6..fa4ab3703 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1316,7 +1316,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, if typeRel(c, f.sons[i], a.sons[i]) == isNone: return isNone result = typeRel(c, f.lastSon, a.lastSon, flags + {trNoCovariance}) subtypeCheck() - if result <= isConvertible: result = isNone + if result <= isIntConv: result = isNone elif tfNotNil in f.flags and tfNotNil notin a.flags: result = isNilConversion elif a.kind == tyNil: result = f.allowsNil diff --git a/compiler/suggest.nim b/compiler/suggest.nim index f3f960136..09eacbbed 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -33,7 +33,7 @@ # included from sigmatch.nim import algorithm, prefixmatches, lineinfos, pathutils -from wordrecg import wDeprecated, wError +from wordrecg import wDeprecated, wError, wAddr, wYield, specialWords when defined(nimsuggest): import passes, tables # importer @@ -109,7 +109,11 @@ proc symToSuggest(conf: ConfigRef; s: PSym, isLocal: bool, section: IdeCmd, info result.qualifiedPath.add(ow2.origModuleName) if ow != nil: result.qualifiedPath.add(ow.origModuleName) - result.qualifiedPath.add(s.name.s) + if s.name.s[0] in OpChars + {'[', '{', '('} or + s.name.id in ord(wAddr)..ord(wYield): + result.qualifiedPath.add('`' & s.name.s & '`') + else: + result.qualifiedPath.add(s.name.s) if s.typ != nil: result.forth = typeToString(s.typ) diff --git a/compiler/types.nim b/compiler/types.nim index 797336ddf..1d6e71f14 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -456,12 +456,18 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = result = $t.n.intVal else: result = "int literal(" & $t.n.intVal & ")" - of tyGenericBody, tyGenericInst, tyGenericInvocation: + of tyGenericInst, tyGenericInvocation: result = typeToString(t.sons[0]) & '[' for i in countup(1, sonsLen(t)-1-ord(t.kind != tyGenericInvocation)): if i > 1: add(result, ", ") add(result, typeToString(t.sons[i], preferGenericArg)) add(result, ']') + of tyGenericBody: + result = typeToString(t.lastSon) & '[' + for i in countup(0, sonsLen(t)-2): + if i > 0: add(result, ", ") + add(result, typeToString(t.sons[i], preferTypeName)) + add(result, ']') of tyTypeDesc: if t.sons[0].kind == tyNone: result = "typedesc" else: result = "type " & typeToString(t.sons[0]) @@ -612,7 +618,6 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = result = typeToStr[t.kind] result.addTypeFlags(t) - proc firstOrd*(conf: ConfigRef; t: PType): BiggestInt = case t.kind of tyBool, tyChar, tySequence, tyOpenArray, tyString, tyVarargs, tyProxy: diff --git a/compiler/vm.nim b/compiler/vm.nim index 10d38fe77..e95a491fd 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -399,6 +399,11 @@ proc opConv(c: PCtx; dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): dest.floatVal = toBiggestFloat(src.intVal) else: dest.floatVal = src.floatVal + of tyObject: + if srctyp.skipTypes(abstractRange).kind != tyObject: + internalError(c.config, "invalid object-to-object conversion") + # A object-to-object conversion is essentially a no-op + moveConst(dest, src) else: asgnComplex(dest, src) @@ -929,7 +934,8 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = stackTrace(c, tos, pc, errNilAccess) of opcGetImpl: decodeB(rkNode) - let a = regs[rb].node + var a = regs[rb].node + if a.kind == nkVarTy: a = a[0] if a.kind == nkSym: regs[ra].node = if a.sym.ast.isNil: newNode(nkNilLit) else: copyTree(a.sym.ast) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index e7993dfb2..f87821da4 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -326,8 +326,14 @@ proc genWhile(c: PCtx; n: PNode) = c.patch(L2) proc genBlock(c: PCtx; n: PNode; dest: var TDest) = + let oldRegisterCount = c.prc.maxSlots withBlock(n.sons[0].sym): c.gen(n.sons[1], dest) + + for i in oldRegisterCount ..< c.prc.maxSlots: + if c.prc.slots[i].kind in {slotFixedVar, slotFixedLet}: + c.prc.slots[i] = (inUse: false, kind: slotEmpty) + c.clearDest(n, dest) proc genBreak(c: PCtx; n: PNode) = |