# # # The Nim Compiler # (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # import intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees, wordrecg, strutils, options, guards, lineinfos, semfold, semdata, modulegraphs when defined(useDfa): import dfa import liftdestructors include sinkparameter_inference #[ Second semantic checking pass over the AST. Necessary because the old way had some inherent problems. Performs: * effect+exception tracking * "usage before definition" checking * also now calls the "lift destructor logic" at strategic positions, this is about to be put into the spec: We treat assignment and sinks and destruction as identical. In the construct let/var x = expr() x's type is marked. In x = y the type of x is marked. For every sink parameter of type T T is marked. For every call f() the return type of f() is marked. ]# # ------------------------ exception and tag tracking ------------------------- discard """ exception tracking: a() # raises 'x', 'e' try: b() # raises 'e' except e: # must not undo 'e' here; hrm c() --> we need a stack of scopes for this analysis # XXX enhance the algorithm to care about 'dirty' expressions: lock a[i].L: inc i # mark 'i' dirty lock a[j].L: access a[i], a[j] # --> reject a[i] """ type TEffects = object exc: PNode # stack of exceptions tags: PNode # list of tags bottom, inTryStmt: int owner: PSym ownerModule: PSym init: seq[int] # list of initialized variables guards: TModel # nested guards locked: seq[PNode] # locked locations gcUnsafe, isRecursive, isTopLevel, hasSideEffect, inEnforcedGcSafe: bool inEnforcedNoSideEffects: bool maxLockLevel, currLockLevel: TLockLevel currOptions: TOptions config: ConfigRef graph: ModuleGraph c: PContext PEffects = var TEffects proc `<`(a, b: TLockLevel): bool {.borrow.} proc `<=`(a, b: TLockLevel): bool {.borrow.} proc `==`(a, b: TLockLevel): bool {.borrow.} proc max(a, b: TLockLevel): TLockLevel {.borrow.} proc isLocalVar(a: PEffects, s: PSym): bool = s.kind in {skVar, skResult} and sfGlobal notin s.flags and s.owner == a.owner and s.typ != nil proc getLockLevel(t: PType): TLockLevel = var t = t # tyGenericInst(TLock {tyGenericBody}, tyStatic, tyObject): if t.kind == tyGenericInst and t.len == 3: t = t[1] if t.kind == tyStatic and t.n != nil and t.n.kind in {nkCharLit..nkInt64Lit}: result = t.n.intVal.TLockLevel proc lockLocations(a: PEffects; pragma: PNode) = if pragma.kind != nkExprColonExpr: localError(a.config, pragma.info, "locks pragma without argument") return var firstLL = TLockLevel(-1'i16) for x in pragma[1]: let thisLL = getLockLevel(x.typ) if thisLL != 0.TLockLevel: if thisLL < 0.TLockLevel or thisLL > MaxLockLevel.TLockLevel: localError(a.config, x.info, "invalid lock level: " & $thisLL) elif firstLL < 0.TLockLevel: firstLL = thisLL elif firstLL != thisLL: localError(a.config, x.info, "multi-lock requires the same static lock level for every operand") a.maxLockLevel = max(a.maxLockLevel, firstLL) a.locked.add x if firstLL >= 0.TLockLevel and firstLL != a.currLockLevel: if a.currLockLevel > 0.TLockLevel and a.currLockLevel <= firstLL: localError(a.config, pragma.info, "invalid nested locking") a.currLockLevel = firstLL proc guardGlobal(a: PEffects; n: PNode; guard: PSym) = # check whether the corresponding lock is held: for L in a.locked: if L.kind == nkSym and L.sym == guard: return # we allow accesses nevertheless in top level statements for # easier initialization: #if a.isTopLevel: # message(n.info, warnUnguardedAccess, renderTree(n)) #else: if not a.isTopLevel: localError(a.config, n.info, "unguarded access: " & renderTree(n)) # 'guard*' are checks which are concerned with 'guard' annotations # (var x{.guard: y.}: int) proc guardDotAccess(a: PEffects; n: PNode) = let ri = n[1] if ri.kind != nkSym or ri.sym.kind != skField: return var g = ri.sym.guard if g.isNil or a.isTopLevel: return # fixup guard: if g.kind == skUnknown: var field: PSym = nil var ty = n[0].typ.skipTypes(abstractPtrs) if ty.kind == tyTuple and not ty.n.isNil: field = lookupInRecord(ty.n, g.name) else: while ty != nil and ty.kind == tyObject: field = lookupInRecord(ty.n, g.name) if field != nil: break ty = ty[0] if ty == nil: break ty = ty.skipTypes(skipPtrs) if field == nil: localError(a.config, n.info, "invalid guard field: " & g.name.s) return g = field #ri.sym.guard = field # XXX unfortunately this is not correct for generic instantiations! if g.kind == skField: let dot = newNodeI(nkDotExpr, n.info, 2) dot[0] = n[0] dot[1] = newSymNode(g) dot.typ = g.typ for L in a.locked: #if a.guards.sameSubexprs(dot, L): return if guards.sameTree(dot, L): return localError(a.config, n.info, "unguarded access: " & renderTree(n)) else: guardGlobal(a, n, g) proc makeVolatile(a: PEffects; s: PSym) {.inline.} = if a.inTryStmt > 0 and a.config.exc == excSetjmp: incl(s.flags, sfVolatile) proc initVar(a: PEffects, n: PNode; volatileCheck: bool) = if n.kind != nkSym: return let s = n.sym if isLocalVar(a, s): if volatileCheck: makeVolatile(a, s) for x in a.init: if x == s.id: return a.init.add s.id proc initVarViaNew(a: PEffects, n: PNode) = if n.kind != nkSym: return let s = n.sym if {tfRequiresInit, tfNotNil} * s.typ.flags <= {tfNotNil}: # 'x' is not nil, but that doesn't mean its "not nil" children # are initialized: initVar(a, n, volatileCheck=true) elif isLocalVar(a, s): makeVolatile(a, s) proc warnAboutGcUnsafe(n: PNode; conf: ConfigRef) = #assert false message(conf, n.info, warnGcUnsafe, renderTree(n)) proc markGcUnsafe(a: PEffects; reason: PSym) = if not a.inEnforcedGcSafe: a.gcUnsafe = true if a.owner.kind in routineKinds: a.owner.gcUnsafetyReason = reason proc markGcUnsafe(a: PEffects; reason: PNode) = if not a.inEnforcedGcSafe: a.gcUnsafe = true if a.owner.kind in routineKinds: if reason.kind == nkSym: a.owner.gcUnsafetyReason = reason.sym else: a.owner.gcUnsafetyReason = newSym(skUnknown, a.owner.name, a.owner, reason.info, {}) when true: template markSideEffect(a: PEffects; reason: typed) = if not a.inEnforcedNoSideEffects: a.hasSideEffect = true else: template markSideEffect(a: PEffects; reason: typed) = if not a.inEnforcedNoSideEffects: a.hasSideEffect = true markGcUnsafe(a, reason) proc listGcUnsafety(s: PSym; onlyWarning: bool; cycleCheck: var IntSet; conf: ConfigRef) = let u = s.gcUnsafetyReason if u != nil and not cycleCheck.containsOrIncl(u.id): let msgKind = if onlyWarning: warnGcUnsafe2 else: errGenerated case u.kind of skLet, skVar: message(conf, s.info, msgKind, ("'$#' is not GC-safe as it accesses '$#'" & " which is a global using GC'ed memory") % [s.name.s, u.name.s]) of routineKinds: # recursive call *always* produces only a warning so the full error # message is printed: listGcUnsafety(u, true, cycleCheck, conf) message(conf, s.info, msgKind, "'$#' is not GC-safe as it calls '$#'" % [s.name.s, u.name.s]) of skParam, skForVar: message(conf, s.info, msgKind, "'$#' is not GC-safe as it performs an indirect call via '$#'" % [s.name.s, u.name.s]) else: message(conf, u.info, msgKind, "'$#' is not GC-safe as it performs an indirect call here" % s.name.s) proc listGcUnsafety(s: PSym; onlyWarning: bool; conf: ConfigRef) = var cycleCheck = initIntSet() listGcUnsafety(s, onlyWarning, cycleCheck, conf) proc useVar(a: PEffects, n: PNode) = let s = n.sym if isLocalVar(a, s): if sfNoInit in s.flags: # If the variable is explicitly marked as .noinit. do not emit any error a.init.add s.id elif s.id notin a.init: if s.typ.requiresInit: message(a.config, n.info, warnProveInit, s.name.s) else: message(a.config, n.info, warnUninit, s.name.s) # prevent superfluous warnings about the same variable: a.init.add s.id if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet} and s.magic != mNimvm: if s.guard != nil: guardGlobal(a, n, s.guard) if {sfGlobal, sfThread} * s.flags == {sfGlobal} and (tfHasGCedMem in s.typ.flags or s.typ.isGCedMem): #if a.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n) markGcUnsafe(a, s) markSideEffect(a, s) else: markSideEffect(a, s) type TIntersection = seq[tuple[id, count: int]] # a simple count table proc addToIntersection(inter: var TIntersection, s: int) = for j in 0.. 0: setLen(tracked.exc.sons, L) else: assert L == 0 proc catchesAll(tracked: PEffects) = if tracked.exc.len > 0: setLen(tracked.exc.sons, tracked.bottom) proc track(tracked: PEffects, n: PNode) proc trackTryStmt(tracked: PEffects, n: PNode) = let oldBottom = tracked.bottom tracked.bottom = tracked.exc.len let oldState = tracked.init.len var inter: TIntersection = @[] inc tracked.inTryStmt track(tracked, n[0]) dec tracked.inTryStmt for i in oldState..= tracked.currLockLevel: # if in lock section: if tracked.currLockLevel > 0.TLockLevel: localError tracked.config, n.info, errGenerated, "expected lock level < " & $tracked.currLockLevel & " but got lock level " & $lockLevel tracked.maxLockLevel = max(tracked.maxLockLevel, lockLevel) proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) = let pragma = s.ast[pragmasPos] let spec = effectSpec(pragma, wRaises) mergeEffects(tracked, spec, n) let tagSpec = effectSpec(pragma, wTags) mergeTags(tracked, tagSpec, n) if notGcSafe(s.typ) and sfImportc notin s.flags: if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config) markGcUnsafe(tracked, s) if tfNoSideEffect notin s.typ.flags: markSideEffect(tracked, s) mergeLockLevels(tracked, n, s.getLockLevel) proc procVarCheck(n: PNode; conf: ConfigRef) = if n.kind in nkSymChoices: for x in n: procVarCheck(x, conf) elif n.kind == nkSym and n.sym.magic != mNone and n.sym.kind in routineKinds: localError(conf, n.info, "'$1' cannot be passed to a procvar" % n.sym.name.s) proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) = let n = n.skipConv if paramType.isNil or paramType.kind != tyTypeDesc: procVarCheck skipConvAndClosure(n), tracked.config #elif n.kind in nkSymChoices: # echo "came here" let paramType = paramType.skipTypesOrNil(abstractInst) if paramType != nil and tfNotNil in paramType.flags and n.typ != nil and tfNotNil notin n.typ.flags: if isAddrNode(n): # addr(x[]) can't be proven, but addr(x) can: if not containsNode(n, {nkDerefExpr, nkHiddenDeref}): return elif (n.kind == nkSym and n.sym.kind in routineKinds) or (n.kind in procDefs+{nkObjConstr, nkBracket, nkClosure, nkStrLit..nkTripleStrLit}) or (n.kind in nkCallKinds and n[0].kind == nkSym and n[0].sym.magic == mArrToSeq) or n.typ.kind == tyTypeDesc: # 'p' is not nil obviously: return case impliesNotNil(tracked.guards, n) of impUnknown: message(tracked.config, n.info, errGenerated, "cannot prove '$1' is not nil" % n.renderTree) of impNo: message(tracked.config, n.info, errGenerated, "'$1' is provably nil" % n.renderTree) of impYes: discard proc assumeTheWorst(tracked: PEffects; n: PNode; op: PType) = addEffect(tracked, createRaise(tracked.graph, n), nil) addTag(tracked, createTag(tracked.graph, n), nil) let lockLevel = if op.lockLevel == UnspecifiedLockLevel: UnknownLockLevel else: op.lockLevel #if lockLevel == UnknownLockLevel: # message(n.info, warnUser, "had to assume the worst here") mergeLockLevels(tracked, n, lockLevel) proc isOwnedProcVar(n: PNode; owner: PSym): bool = # XXX prove the soundness of this effect system rule result = n.kind == nkSym and n.sym.kind == skParam and owner == n.sym.owner proc isNoEffectList(n: PNode): bool {.inline.} = assert n.kind == nkEffectList n.len == 0 or (n[tagEffects] == nil and n[exceptionEffects] == nil) proc isTrival(caller: PNode): bool {.inline.} = result = caller.kind == nkSym and caller.sym.magic in {mEqProc, mIsNil, mMove, mWasMoved} proc trackOperand(tracked: PEffects, n: PNode, paramType: PType; caller: PNode) = let a = skipConvAndClosure(n) let op = a.typ # assume indirect calls are taken here: if op != nil and op.kind == tyProc and n.skipConv.kind != nkNilLit and not isTrival(caller): internalAssert tracked.config, op.n[0].kind == nkEffectList var effectList = op.n[0] var s = n.skipConv if s.kind == nkCast and s[1].typ.kind == tyProc: s = s[1] if s.kind == nkSym and s.sym.kind in routineKinds and isNoEffectList(effectList): propagateEffects(tracked, n, s.sym) elif isNoEffectList(effectList): if isForwardedProc(n): # we have no explicit effects but it's a forward declaration and so it's # stated there are no additional effects, so simply propagate them: propagateEffects(tracked, n, n.sym) elif not isOwnedProcVar(a, tracked.owner): # we have no explicit effects so assume the worst: assumeTheWorst(tracked, n, op) # assume GcUnsafe unless in its type; 'forward' does not matter: if notGcSafe(op) and not isOwnedProcVar(a, tracked.owner): if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config) markGcUnsafe(tracked, a) elif tfNoSideEffect notin op.flags and not isOwnedProcVar(a, tracked.owner): markSideEffect(tracked, a) else: mergeEffects(tracked, effectList[exceptionEffects], n) mergeTags(tracked, effectList[tagEffects], n) if notGcSafe(op): if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config) markGcUnsafe(tracked, a) elif tfNoSideEffect notin op.flags: markSideEffect(tracked, a) if paramType != nil and paramType.kind == tyVar: invalidateFacts(tracked.guards, n) if n.kind == nkSym and isLocalVar(tracked, n.sym): makeVolatile(tracked, n.sym) if paramType != nil and paramType.kind == tyProc and tfGcSafe in paramType.flags: let argtype = skipTypes(a.typ, abstractInst) # XXX figure out why this can be a non tyProc here. See httpclient.nim for an # example that triggers it. if argtype.kind == tyProc and notGcSafe(argtype) and not tracked.inEnforcedGcSafe: localError(tracked.config, n.info, $n & " is not GC safe") notNilCheck(tracked, n, paramType) proc breaksBlock(n: PNode): bool = # semantic check doesn't allow statements after raise, break, return or # call to noreturn proc, so it is safe to check just the last statements var it = n while it.kind in {nkStmtList, nkStmtListExpr} and it.len > 0: it = it.lastSon result = it.kind in {nkBreakStmt, nkReturnStmt, nkRaiseStmt} or it.kind in nkCallKinds and it[0].kind == nkSym and sfNoReturn in it[0].sym.flags proc trackCase(tracked: PEffects, n: PNode) = track(tracked, n[0]) let oldState = tracked.init.len let oldFacts = tracked.guards.s.len let stringCase = n[0].typ != nil and skipTypes(n[0].typ, abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString} let interesting = not stringCase and interestingCaseExpr(n[0]) and tracked.config.hasWarn(warnProveField) var inter: TIntersection = @[] var toCover = 0 for i in 1..= toCover: tracked.init.add id # else we can't merge setLen(tracked.guards.s, oldFacts) proc trackIf(tracked: PEffects, n: PNode) = track(tracked, n[0][0]) let oldFacts = tracked.guards.s.len addFact(tracked.guards, n[0][0]) let oldState = tracked.init.len var inter: TIntersection = @[] var toCover = 0 track(tracked, n[0][1]) if not breaksBlock(n[0][1]): inc toCover for i in oldState.. 1: addFact(tracked.guards, branch[0]) setLen(tracked.init, oldState) for i in 0..= toCover: tracked.init.add id # else we can't merge as it is not exhaustive setLen(tracked.guards.s, oldFacts) proc trackBlock(tracked: PEffects, n: PNode) = if n.kind in {nkStmtList, nkStmtListExpr}: var oldState = -1 for i in 0.. 'y' not defined after block! if oldState < 0: oldState = tracked.init.len track(tracked, n[i]) if oldState > 0: setLen(tracked.init, oldState) else: track(tracked, n) proc paramType(op: PType, i: int): PType = if op != nil and i < op.len: result = op[i] proc cstringCheck(tracked: PEffects; n: PNode) = if n[0].typ.kind == tyCString and (let a = skipConv(n[1]); a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}): message(tracked.config, n.info, warnUnsafeCode, renderTree(n)) proc patchResult(c: PEffects; n: PNode) = if n.kind == nkSym and n.sym.kind == skResult: let fn = c.owner if fn != nil and fn.kind in routineKinds and fn.ast != nil and resultPos < fn.ast.len: n.sym = fn.ast[resultPos].sym else: localError(c.config, n.info, "routine has no return type, but .requires contains 'result'") else: for i in 0.. " & $b) proc checkBounds(c: PEffects; arr, idx: PNode) = checkLe(c, lowBound(c.config, arr), idx) checkLe(c, idx, highBound(c.config, arr, c.guards.o)) proc checkRange(c: PEffects; value: PNode; typ: PType) = let t = typ.skipTypes(abstractInst - {tyRange}) if t.kind == tyRange: let lowBound = copyTree(t.n[0]) lowBound.info = value.info let highBound = copyTree(t.n[1]) highBound.info = value.info checkLe(c, lowBound, value) checkLe(c, value, highBound) proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo) = if typ == nil: return let realType = typ.skipTypes(abstractInst) when false: # XXX fix this in liftdestructors instead if realType.kind == tyRef and optSeqDestructors in tracked.config.globalOptions: createTypeBoundOps(tracked.graph, tracked.c, realType.lastSon, info) createTypeBoundOps(tracked.graph, tracked.c, typ, info) if (tfHasAsgn in typ.flags) or optSeqDestructors in tracked.config.globalOptions: tracked.owner.flags.incl sfInjectDestructors proc track(tracked: PEffects, n: PNode) = template gcsafeAndSideeffectCheck() = if notGcSafe(op) and not importedFromC(a): # and it's not a recursive call: if not (a.kind == nkSym and a.sym == tracked.owner): if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config) markGcUnsafe(tracked, a) if tfNoSideEffect notin op.flags and not importedFromC(a): # and it's not a recursive call: if not (a.kind == nkSym and a.sym == tracked.owner): markSideEffect(tracked, a) case n.kind of nkSym: useVar(tracked, n) if n.sym.typ != nil and tfHasAsgn in n.sym.typ.flags: tracked.owner.flags.incl sfInjectDestructors of nkRaiseStmt: if n[0].kind != nkEmpty: n[0].info = n.info #throws(tracked.exc, n[0]) addEffect(tracked, n[0], nil) for i in 0.. 0: createTypeBoundOps(tracked, n[1].typ.lastSon, n.info) createTypeBoundOps(tracked, n[1].typ, n.info) # new(x, finalizer): Problem: how to move finalizer into 'createTypeBoundOps'? elif a.kind == nkSym and a.sym.magic in {mArrGet, mArrPut} and optStaticBoundsCheck in tracked.currOptions: checkBounds(tracked, n[1], n[2]) if a.kind == nkSym and a.sym.name.s.len > 0 and a.sym.name.s[0] == '=' and tracked.owner.kind != skMacro: let opKind = find(AttachedOpToStr, a.sym.name.s.normalize) if opKind != -1: # rebind type bounds operations after createTypeBoundOps call let t = n[1].typ.skipTypes({tyAlias, tyVar}) if a.sym != t.attachedOps[TTypeAttachedOp(opKind)]: createTypeBoundOps(tracked, t, n.info) let op = t.attachedOps[TTypeAttachedOp(opKind)] if op != nil: n[0].sym = op for i in 0.. 1: # XXX this is a bit hacky: if iterCall[1].typ != nil and iterCall[1].typ.skipTypes(abstractVar).kind notin {tyVarargs, tyOpenArray}: createTypeBoundOps(tracked, iterCall[1].typ, iterCall[1].info) track(tracked, iterCall) track(tracked, loopBody) setLen(tracked.init, oldState) setLen(tracked.guards.s, oldFacts) of nkObjConstr: when false: track(tracked, n[0]) let oldFacts = tracked.guards.s.len for i in 1.. disp.typ.lockLevel: when true: message(g.config, branch.info, warnLockLevel, "base method has lock level $1, but dispatcher has $2" % [$branch.typ.lockLevel, $disp.typ.lockLevel]) else: # XXX make this an error after bigbreak has been released: localError(g.config, branch.info, "base method has lock level $1, but dispatcher has $2" % [$branch.typ.lockLevel, $disp.typ.lockLevel]) proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) = var effects = t.n[0] if t.kind != tyProc or effects.kind != nkEffectList: return if n.kind != nkEmpty: internalAssert g.config, effects.len == 0 newSeq(effects.sons, effectListLen) let raisesSpec = effectSpec(n, wRaises) if not isNil(raisesSpec): effects[exceptionEffects] = raisesSpec let tagsSpec = effectSpec(n, wTags) if not isNil(tagsSpec): effects[tagEffects] = tagsSpec let requiresSpec = propSpec(n, wRequires) if not isNil(requiresSpec): effects[requiresEffects] = requiresSpec let ensuresSpec = propSpec(n, wEnsures) if not isNil(ensuresSpec): effects[ensuresEffects] = ensuresSpec effects[pragmasEffects] = n proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects; c: PContext) = newSeq(effects.sons, effectListLen) effects[exceptionEffects] = newNodeI(nkArgList, s.info) effects[tagEffects] = newNodeI(nkArgList, s.info) effects[requiresEffects] = g.emptyNode effects[ensuresEffects] = g.emptyNode effects[pragmasEffects] = g.emptyNode t.exc = effects[exceptionEffects] t.tags = effects[tagEffects] t.owner = s t.ownerModule = s.getModule t.init = @[] t.guards.s = @[] t.guards.o = initOperators(g) when defined(drnim): t.currOptions = g.config.options + s.options - {optStaticBoundsCheck} else: t.currOptions = g.config.options + s.options t.guards.beSmart = optStaticBoundsCheck in t.currOptions t.locked = @[] t.graph = g t.config = g.config t.c = c proc trackProc*(c: PContext; s: PSym, body: PNode) = let g = c.graph var effects = s.typ.n[0] if effects.kind != nkEffectList: return # effects already computed? if sfForward in s.flags: return if effects.len == effectListLen: return var t: TEffects initEffects(g, effects, s, t, c) track(t, body) if s.kind != skMacro: let params = s.typ.n for i in 1.. s.typ.lockLevel: #localError(s.info, message(g.config, s.info, warnLockLevel, "declared lock level is $1, but real lock level is $2" % [$s.typ.lockLevel, $t.maxLockLevel]) when defined(drnim): if c.graph.strongSemCheck != nil: c.graph.strongSemCheck(c.graph, s, body) when defined(useDfa): if s.name.s == "testp": dataflowAnalysis(s, body) when false: trackWrites(s, body) proc trackStmt*(c: PContext; module: PSym; n: PNode, isTopLevel: bool) = if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef, nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}: return let g = c.graph var effects = newNode(nkEffectList, n.info) var t: TEffects initEffects(g, effects, module, t, c) t.isTopLevel = isTopLevel track(t, n) when defined(drnim): if c.graph.strongSemCheck != nil: c.graph.strongSemCheck(c.graph, module, n)