diff options
Diffstat (limited to 'compiler/semstmts.nim')
-rw-r--r-- | compiler/semstmts.nim | 2901 |
1 files changed, 2901 insertions, 0 deletions
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim new file mode 100644 index 000000000..f5f8fea0c --- /dev/null +++ b/compiler/semstmts.nim @@ -0,0 +1,2901 @@ +# +# +# The Nim Compiler +# (c) Copyright 2013 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## this module does the semantic checking of statements +# included from sem.nim + +const + errNoSymbolToBorrowFromFound = "no symbol to borrow from found" + errDiscardValueX = "value of type '$1' has to be used (or discarded)" + errInvalidDiscard = "statement returns no value that can be discarded" + errInvalidControlFlowX = "invalid control flow: $1" + errSelectorMustBeOfCertainTypes = "selector must be of an ordinal type, float or string" + errExprCannotBeRaised = "only a 'ref object' can be raised" + errBreakOnlyInLoop = "'break' only allowed in loop construct" + errExceptionAlreadyHandled = "exception already handled" + errYieldNotAllowedHere = "'yield' only allowed in an iterator" + errYieldNotAllowedInTryStmt = "'yield' cannot be used within 'try' in a non-inlined iterator" + errInvalidNumberOfYieldExpr = "invalid number of 'yield' expressions" + errCannotReturnExpr = "current routine cannot return an expression" + errGenericLambdaNotAllowed = "A nested proc can have generic parameters only when " & + "it is used as an operand to another routine and the types " & + "of the generic paramers can be inferred from the expected signature." + errCannotInferTypeOfTheLiteral = "cannot infer the type of the $1" + errCannotInferReturnType = "cannot infer the return type of '$1'" + errCannotInferStaticParam = "cannot infer the value of the static param '$1'" + errProcHasNoConcreteType = "'$1' doesn't have a concrete type, due to unspecified generic parameters." + errLetNeedsInit = "'let' symbol requires an initialization" + errThreadvarCannotInit = "a thread var cannot be initialized explicitly; this would only run for the main thread" + errImplOfXexpected = "implementation of '$1' expected" + errRecursiveDependencyX = "recursive dependency: '$1'" + errRecursiveDependencyIteratorX = "recursion is not supported in iterators: '$1'" + errPragmaOnlyInHeaderOfProcX = "pragmas are only allowed in the header of a proc; redefinition of $1" + errCannotAssignToGlobal = "cannot assign local to global variable" + +proc implicitlyDiscardable(n: PNode): bool + +proc hasEmpty(typ: PType): bool = + if typ.kind in {tySequence, tyArray, tySet}: + result = typ.elementType.kind == tyEmpty + elif typ.kind == tyTuple: + result = false + for s in typ.kids: + result = result or hasEmpty(s) + else: + result = false + +proc semDiscard(c: PContext, n: PNode): PNode = + result = n + checkSonsLen(n, 1, c.config) + if n[0].kind != nkEmpty: + n[0] = semExprWithType(c, n[0]) + let sonType = n[0].typ + let sonKind = n[0].kind + if isEmptyType(sonType) or hasEmpty(sonType) or + sonType.kind in {tyNone, tyTypeDesc} or + sonKind == nkTypeOfExpr: + localError(c.config, n.info, errInvalidDiscard) + if sonType.kind == tyProc and sonKind notin nkCallKinds: + # tyProc is disallowed to prevent ``discard foo`` to be valid, when ``discard foo()`` is meant. + localError(c.config, n.info, "illegal discard proc, did you mean: " & $n[0] & "()") + +proc semBreakOrContinue(c: PContext, n: PNode): PNode = + result = n + checkSonsLen(n, 1, c.config) + if n[0].kind != nkEmpty: + if n.kind != nkContinueStmt: + var s: PSym = nil + case n[0].kind + of nkIdent: s = lookUp(c, n[0]) + of nkSym: s = n[0].sym + else: illFormedAst(n, c.config) + s = getGenSym(c, s) + if s.kind == skLabel and s.owner.id == c.p.owner.id: + var x = newSymNode(s) + x.info = n.info + incl(s.flags, sfUsed) + n[0] = x + suggestSym(c.graph, x.info, s, c.graph.usageSym) + onUse(x.info, s) + else: + localError(c.config, n.info, errInvalidControlFlowX % s.name.s) + else: + localError(c.config, n.info, errGenerated, "'continue' cannot have a label") + elif c.p.nestedBlockCounter > 0 and n.kind == nkBreakStmt and not c.p.breakInLoop: + localError(c.config, n.info, warnUnnamedBreak) + elif (c.p.nestedLoopCounter <= 0) and ((c.p.nestedBlockCounter <= 0) or n.kind == nkContinueStmt): + localError(c.config, n.info, errInvalidControlFlowX % + renderTree(n, {renderNoComments})) + +proc semAsm(c: PContext, n: PNode): PNode = + checkSonsLen(n, 2, c.config) + var marker = pragmaAsm(c, n[0]) + if marker == '\0': marker = '`' # default marker + result = semAsmOrEmit(c, n, marker) + +proc semWhile(c: PContext, n: PNode; flags: TExprFlags): PNode = + result = n + checkSonsLen(n, 2, c.config) + openScope(c) + n[0] = forceBool(c, semExprWithType(c, n[0], expectedType = getSysType(c.graph, n.info, tyBool))) + inc(c.p.nestedLoopCounter) + let oldBreakInLoop = c.p.breakInLoop + c.p.breakInLoop = true + n[1] = semStmt(c, n[1], flags) + c.p.breakInLoop = oldBreakInLoop + dec(c.p.nestedLoopCounter) + closeScope(c) + if n[1].typ == c.enforceVoidContext: + result.typ = c.enforceVoidContext + elif efInTypeof in flags: + result.typ = n[1].typ + elif implicitlyDiscardable(n[1]): + result[1].typ = c.enforceVoidContext + +proc semProc(c: PContext, n: PNode): PNode + +proc semExprBranch(c: PContext, n: PNode; flags: TExprFlags = {}; expectedType: PType = nil): PNode = + result = semExpr(c, n, flags, expectedType) + if result.typ != nil: + # XXX tyGenericInst here? + if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) + +proc semExprBranchScope(c: PContext, n: PNode; expectedType: PType = nil): PNode = + openScope(c) + result = semExprBranch(c, n, expectedType = expectedType) + closeScope(c) + +const + skipForDiscardable = {nkStmtList, nkStmtListExpr, + nkOfBranch, nkElse, nkFinally, nkExceptBranch, + nkElifBranch, nkElifExpr, nkElseExpr, nkBlockStmt, nkBlockExpr, + nkHiddenStdConv, nkHiddenSubConv, nkHiddenDeref} + +proc implicitlyDiscardable(n: PNode): bool = + # same traversal as endsInNoReturn + template checkBranch(branch) = + if not implicitlyDiscardable(branch): + return false + + var it = n + # skip these beforehand, no special handling needed + while it.kind in skipForDiscardable and it.len > 0: + it = it.lastSon + + case it.kind + of nkIfExpr, nkIfStmt: + for branch in it: + checkBranch: + if branch.len == 2: + branch[1] + elif branch.len == 1: + branch[0] + else: + raiseAssert "Malformed `if` statement during implicitlyDiscardable" + # all branches are discardable + result = true + of nkCaseStmt: + for i in 1 ..< it.len: + let branch = it[i] + checkBranch: + case branch.kind + of nkOfBranch: + branch[^1] + of nkElifBranch: + branch[1] + of nkElse: + branch[0] + else: + raiseAssert "Malformed `case` statement in implicitlyDiscardable" + # all branches are discardable + result = true + of nkTryStmt: + checkBranch(it[0]) + for i in 1 ..< it.len: + let branch = it[i] + if branch.kind != nkFinally: + checkBranch(branch[^1]) + # all branches are discardable + result = true + of nkCallKinds: + result = it[0].kind == nkSym and {sfDiscardable, sfNoReturn} * it[0].sym.flags != {} + of nkLastBlockStmts: + result = true + else: + result = false + +proc endsInNoReturn(n: PNode, returningNode: var PNode; discardableCheck = false): bool = + ## check if expr ends the block like raising or call of noreturn procs do + result = false # assume it does return + + template checkBranch(branch) = + if not endsInNoReturn(branch, returningNode, discardableCheck): + # proved a branch returns + return false + + var it = n + # skip these beforehand, no special handling needed + let skips = if discardableCheck: skipForDiscardable else: skipForDiscardable-{nkBlockExpr, nkBlockStmt} + while it.kind in skips and it.len > 0: + it = it.lastSon + + case it.kind + of nkIfExpr, nkIfStmt: + var hasElse = false + for branch in it: + checkBranch: + if branch.len == 2: + branch[1] + elif branch.len == 1: + hasElse = true + branch[0] + else: + raiseAssert "Malformed `if` statement during endsInNoReturn" + # none of the branches returned + result = hasElse # Only truly a no-return when it's exhaustive + of nkCaseStmt: + let caseTyp = skipTypes(it[0].typ, abstractVar-{tyTypeDesc}) + # semCase should already have checked for exhaustiveness in this case + # effectively the same as having an else + var hasElse = caseTyp.shouldCheckCaseCovered() + + # actual noreturn checks + for i in 1 ..< it.len: + let branch = it[i] + checkBranch: + case branch.kind + of nkOfBranch: + branch[^1] + of nkElifBranch: + branch[1] + of nkElse: + hasElse = true + branch[0] + else: + raiseAssert "Malformed `case` statement in endsInNoReturn" + # Can only guarantee a noreturn if there is an else or it's exhaustive + result = hasElse + of nkTryStmt: + checkBranch(it[0]) + var lastIndex = it.len - 1 + if it[lastIndex].kind == nkFinally: + # if finally is noreturn, then the entire statement is noreturn + if endsInNoReturn(it[lastIndex][^1], returningNode, discardableCheck): + return true + dec lastIndex + for i in 1 .. lastIndex: + let branch = it[i] + checkBranch(branch[^1]) + # none of the branches returned + result = true + of nkLastBlockStmts: + result = true + of nkCallKinds: + result = it[0].kind == nkSym and sfNoReturn in it[0].sym.flags + if not result: + returningNode = it + else: + result = false + returningNode = it + +proc endsInNoReturn(n: PNode): bool = + var dummy: PNode = nil + result = endsInNoReturn(n, dummy) + +proc fixNilType(c: PContext; n: PNode) = + if isAtom(n): + if n.kind != nkNilLit and n.typ != nil: + localError(c.config, n.info, errDiscardValueX % n.typ.typeToString) + elif n.kind in {nkStmtList, nkStmtListExpr}: + n.transitionSonsKind(nkStmtList) + for it in n: fixNilType(c, it) + n.typ = nil + +proc discardCheck(c: PContext, result: PNode, flags: TExprFlags) = + if c.matchedConcept != nil or efInTypeof in flags: return + + if result.typ != nil and result.typ.kind notin {tyTyped, tyVoid}: + if implicitlyDiscardable(result): + var n = newNodeI(nkDiscardStmt, result.info, 1) + n[0] = result + # notes that it doesn't transform nodes into discard statements + elif result.typ.kind != tyError and c.config.cmd != cmdInteractive: + if result.typ.kind == tyNone: + localError(c.config, result.info, "expression has no type: " & + renderTree(result, {renderNoComments})) + else: + # Ignore noreturn procs since they don't have a type + var n = result + if result.endsInNoReturn(n, discardableCheck = true): + return + + var s = "expression '" & $n & "' is of type '" & + result.typ.typeToString & "' and has to be used (or discarded)" + if result.info.line != n.info.line or + result.info.fileIndex != n.info.fileIndex: + s.add "; start of expression here: " & c.config$result.info + if result.typ.kind == tyProc: + s.add "; for a function call use ()" + localError(c.config, n.info, s) + +proc semIf(c: PContext, n: PNode; flags: TExprFlags; expectedType: PType = nil): PNode = + result = n + var typ = commonTypeBegin + var expectedType = expectedType + var hasElse = false + for i in 0..<n.len: + var it = n[i] + if it.len == 2: + openScope(c) + it[0] = forceBool(c, semExprWithType(c, it[0], expectedType = getSysType(c.graph, n.info, tyBool))) + it[1] = semExprBranch(c, it[1], flags, expectedType) + typ = commonType(c, typ, it[1]) + if not endsInNoReturn(it[1]): + expectedType = typ + closeScope(c) + elif it.len == 1: + hasElse = true + it[0] = semExprBranchScope(c, it[0], expectedType) + typ = commonType(c, typ, it[0]) + if not endsInNoReturn(it[0]): + expectedType = typ + else: illFormedAst(it, c.config) + if isEmptyType(typ) or typ.kind in {tyNil, tyUntyped} or + (not hasElse and efInTypeof notin flags): + for it in n: discardCheck(c, it.lastSon, flags) + result.transitionSonsKind(nkIfStmt) + # propagate any enforced VoidContext: + if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext + else: + for it in n: + let j = it.len-1 + if not endsInNoReturn(it[j]): + it[j] = fitNode(c, typ, it[j], it[j].info) + result.transitionSonsKind(nkIfExpr) + result.typ = typ + +proc semTry(c: PContext, n: PNode; flags: TExprFlags; expectedType: PType = nil): PNode = + var check = initIntSet() + template semExceptBranchType(typeNode: PNode): bool = + # returns true if exception type is imported type + let typ = semTypeNode(c, typeNode, nil).toObject() + var isImported = false + if isImportedException(typ, c.config): + isImported = true + elif not isException(typ): + localError(c.config, typeNode.info, errExprCannotBeRaised) + elif not isDefectOrCatchableError(typ): + message(c.config, a.info, warnBareExcept, "catch a more precise Exception deriving from CatchableError or Defect.") + + if containsOrIncl(check, typ.id): + localError(c.config, typeNode.info, errExceptionAlreadyHandled) + typeNode = newNodeIT(nkType, typeNode.info, typ) + isImported + + result = n + checkMinSonsLen(n, 2, c.config) + + var typ = commonTypeBegin + var expectedType = expectedType + n[0] = semExprBranchScope(c, n[0], expectedType) + if not endsInNoReturn(n[0]): + typ = commonType(c, typ, n[0].typ) + expectedType = typ + + var last = n.len - 1 + var catchAllExcepts = 0 + + for i in 1..last: + let a = n[i] + checkMinSonsLen(a, 1, c.config) + openScope(c) + if a.kind == nkExceptBranch: + + if a.len == 2 and a[0].kind == nkBracket: + # rewrite ``except [a, b, c]: body`` -> ```except a, b, c: body``` + a.sons[0..0] = move a[0].sons + + if a.len == 2 and a[0].isInfixAs(): + # support ``except Exception as ex: body`` + let isImported = semExceptBranchType(a[0][1]) + let symbol = newSymG(skLet, a[0][2], c) + symbol.typ = if isImported: a[0][1].typ + else: a[0][1].typ.toRef(c.idgen) + addDecl(c, symbol) + # Overwrite symbol in AST with the symbol in the symbol table. + a[0][2] = newSymNode(symbol, a[0][2].info) + + elif a.len == 1: + # count number of ``except: body`` blocks + inc catchAllExcepts + message(c.config, a.info, warnBareExcept, + "The bare except clause is deprecated; use `except CatchableError:` instead") + else: + # support ``except KeyError, ValueError, ... : body`` + if catchAllExcepts > 0: + # if ``except: body`` already encountered, + # cannot be followed by a ``except KeyError, ... : body`` block + inc catchAllExcepts + var isNative, isImported: bool = false + for j in 0..<a.len-1: + let tmp = semExceptBranchType(a[j]) + if tmp: isImported = true + else: isNative = true + + if isNative and isImported: + localError(c.config, a[0].info, "Mix of imported and native exception types is not allowed in one except branch") + + elif a.kind == nkFinally: + if i != n.len-1: + localError(c.config, a.info, "Only one finally is allowed after all other branches") + + else: + illFormedAst(n, c.config) + + if catchAllExcepts > 1: + # if number of ``except: body`` blocks is greater than 1 + # or more specific exception follows a general except block, it is invalid + localError(c.config, a.info, "Only one general except clause is allowed after more specific exceptions") + + # last child of an nkExcept/nkFinally branch is a statement: + if a.kind != nkFinally: + a[^1] = semExprBranchScope(c, a[^1], expectedType) + typ = commonType(c, typ, a[^1]) + if not endsInNoReturn(a[^1]): + expectedType = typ + else: + a[^1] = semExprBranchScope(c, a[^1]) + dec last + closeScope(c) + + if isEmptyType(typ) or typ.kind in {tyNil, tyUntyped}: + discardCheck(c, n[0], flags) + for i in 1..<n.len: discardCheck(c, n[i].lastSon, flags) + if typ == c.enforceVoidContext: + result.typ = c.enforceVoidContext + else: + if n.lastSon.kind == nkFinally: discardCheck(c, n.lastSon.lastSon, flags) + if not endsInNoReturn(n[0]): + n[0] = fitNode(c, typ, n[0], n[0].info) + for i in 1..last: + var it = n[i] + let j = it.len-1 + if not endsInNoReturn(it[j]): + it[j] = fitNode(c, typ, it[j], it[j].info) + result.typ = typ + +proc fitRemoveHiddenConv(c: PContext, typ: PType, n: PNode): PNode = + result = fitNode(c, typ, n, n.info) + if result.kind in {nkHiddenStdConv, nkHiddenSubConv}: + let r1 = result[1] + if r1.kind in {nkCharLit..nkUInt64Lit} and typ.skipTypes(abstractRange).kind in {tyFloat..tyFloat128}: + result = newFloatNode(nkFloatLit, BiggestFloat r1.intVal) + result.info = n.info + result.typ = typ + if not floatRangeCheck(result.floatVal, typ): + localError(c.config, n.info, errFloatToString % [$result.floatVal, typeToString(typ)]) + elif r1.kind == nkSym and typ.skipTypes(abstractRange).kind == tyCstring: + discard "keep nkHiddenStdConv for cstring conversions" + else: + changeType(c, r1, typ, check=true) + result = r1 + elif not sameType(result.typ, typ): + changeType(c, result, typ, check=false) + +proc findShadowedVar(c: PContext, v: PSym): PSym = + result = nil + for scope in localScopesFrom(c, c.currentScope.parent): + let shadowed = strTableGet(scope.symbols, v.name) + if shadowed != nil and shadowed.kind in skLocalVars: + return shadowed + +proc identWithin(n: PNode, s: PIdent): bool = + for i in 0..n.safeLen-1: + if identWithin(n[i], s): return true + result = n.kind == nkSym and n.sym.name.id == s.id + +proc semIdentDef(c: PContext, n: PNode, kind: TSymKind, reportToNimsuggest = true): PSym = + if isTopLevel(c): + result = semIdentWithPragma(c, kind, n, {sfExported}, fromTopLevel = true) + incl(result.flags, sfGlobal) + #if kind in {skVar, skLet}: + # echo "global variable here ", n.info, " ", result.name.s + else: + result = semIdentWithPragma(c, kind, n, {}) + if result.owner.kind == skModule: + incl(result.flags, sfGlobal) + result.options = c.config.options + + proc getLineInfo(n: PNode): TLineInfo = + case n.kind + of nkPostfix: + if len(n) > 1: + return getLineInfo(n[1]) + of nkAccQuoted, nkPragmaExpr: + if len(n) > 0: + return getLineInfo(n[0]) + else: + discard + result = n.info + let info = getLineInfo(n) + if reportToNimsuggest: + suggestSym(c.graph, info, result, c.graph.usageSym) + +proc checkNilable(c: PContext; v: PSym) = + if {sfGlobal, sfImportc} * v.flags == {sfGlobal} and v.typ.requiresInit: + if v.astdef.isNil: + message(c.config, v.info, warnProveInit, v.name.s) + elif tfNotNil in v.typ.flags and not v.astdef.typ.isNil and tfNotNil notin v.astdef.typ.flags: + message(c.config, v.info, warnProveInit, v.name.s) + +#include liftdestructors + +proc addToVarSection(c: PContext; result: var PNode; n: PNode) = + if result.kind != nkStmtList: + result = makeStmtList(result) + result.add n + +proc addToVarSection(c: PContext; result: var PNode; orig, identDefs: PNode) = + if result.kind == nkStmtList: + let o = copyNode(orig) + o.add identDefs + result.add o + else: + result.add identDefs + +proc isDiscardUnderscore(v: PSym): bool = + if v.name.id == ord(wUnderscore): + v.flags.incl(sfGenSym) + result = true + else: + result = false + +proc semUsing(c: PContext; n: PNode): PNode = + result = c.graph.emptyNode + if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "using") + for i in 0..<n.len: + var a = n[i] + if c.config.cmd == cmdIdeTools: suggestStmt(c, a) + if a.kind == nkCommentStmt: continue + if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config) + checkMinSonsLen(a, 3, c.config) + if a[^2].kind != nkEmpty: + let typ = semTypeNode(c, a[^2], nil) + for j in 0..<a.len-2: + let v = semIdentDef(c, a[j], skParam) + styleCheckDef(c, v) + onDef(a[j].info, v) + v.typ = typ + strTableIncl(c.signatures, v) + else: + localError(c.config, a.info, "'using' section must have a type") + var def: PNode + if a[^1].kind != nkEmpty: + localError(c.config, a.info, "'using' sections cannot contain assignments") + +proc hasUnresolvedParams(n: PNode; flags: TExprFlags): bool = + result = tfUnresolved in n.typ.flags + when false: + case n.kind + of nkSym: + result = isGenericRoutineStrict(n.sym) + of nkSymChoices: + for ch in n: + if hasUnresolvedParams(ch, flags): + return true + result = false + else: + result = false + if efOperand in flags: + if tfUnresolved notin n.typ.flags: + result = false + +proc makeDeref(n: PNode): PNode = + var t = n.typ + if t.kind in tyUserTypeClasses and t.isResolvedUserTypeClass: + t = t.last + t = skipTypes(t, {tyGenericInst, tyAlias, tySink, tyOwned}) + result = n + if t.kind in {tyVar, tyLent}: + result = newNodeIT(nkHiddenDeref, n.info, t.elementType) + result.add n + t = skipTypes(t.elementType, {tyGenericInst, tyAlias, tySink, tyOwned}) + while t.kind in {tyPtr, tyRef}: + var a = result + let baseTyp = t.elementType + result = newNodeIT(nkHiddenDeref, n.info, baseTyp) + result.add a + t = skipTypes(baseTyp, {tyGenericInst, tyAlias, tySink, tyOwned}) + +proc fillPartialObject(c: PContext; n: PNode; typ: PType) = + if n.len == 2: + let x = semExprWithType(c, n[0]) + let y = considerQuotedIdent(c, n[1]) + let obj = x.typ.skipTypes(abstractPtrs) + if obj.kind == tyObject and tfPartial in obj.flags: + let field = newSym(skField, getIdent(c.cache, y.s), c.idgen, obj.sym, n[1].info) + field.typ = skipIntLit(typ, c.idgen) + field.position = obj.n.len + obj.n.add newSymNode(field) + n[0] = makeDeref x + n[1] = newSymNode(field) + n.typ = field.typ + else: + localError(c.config, n.info, "implicit object field construction " & + "requires a .partial object, but got " & typeToString(obj)) + else: + localError(c.config, n.info, "nkDotNode requires 2 children") + +proc setVarType(c: PContext; v: PSym, typ: PType) = + if v.typ != nil and not sameTypeOrNil(v.typ, typ): + localError(c.config, v.info, "inconsistent typing for reintroduced symbol '" & + v.name.s & "': previous type was: " & typeToString(v.typ, preferDesc) & + "; new type is: " & typeToString(typ, preferDesc)) + v.typ = typ + +proc isPossibleMacroPragma(c: PContext, it: PNode, key: PNode): bool = + # make sure it's not a normal pragma, and calls an identifier + # considerQuotedIdent below will fail on non-identifiers + result = whichPragma(it) == wInvalid and key.kind in nkIdentKinds+{nkDotExpr} + if result: + # make sure it's not a user pragma + if key.kind != nkDotExpr: + let ident = considerQuotedIdent(c, key) + result = strTableGet(c.userPragmas, ident) == nil + if result: + # make sure it's not a custom pragma + let sym = qualifiedLookUp(c, key, {}) + result = sym == nil or sfCustomPragma notin sym.flags + +proc copyExcept(n: PNode, i: int): PNode = + result = copyNode(n) + for j in 0..<n.len: + if j != i: result.add(n[j]) + +proc semVarMacroPragma(c: PContext, a: PNode, n: PNode): PNode = + # Mirrored with semProcAnnotation + result = nil + # a, b {.prag.}: int = 3 not allowed + const lhsPos = 0 + if a.len == 3 and a[lhsPos].kind == nkPragmaExpr: + var b = a[lhsPos] + const + namePos = 0 + pragmaPos = 1 + let pragmas = b[pragmaPos] + for i in 0 ..< pragmas.len: + let it = pragmas[i] + let key = if it.kind in nkPragmaCallKinds and it.len >= 1: it[0] else: it + + trySuggestPragmas(c, key) + + if isPossibleMacroPragma(c, it, key): + # we transform ``var p {.m, rest.}`` into ``m(do: var p {.rest.})`` and + # let the semantic checker deal with it: + var x = newNodeI(nkCall, key.info) + x.add(key) + + if it.kind in nkPragmaCallKinds and it.len > 1: + # pass pragma arguments to the macro too: + for i in 1..<it.len: + x.add(it[i]) + + # Drop the pragma from the list, this prevents getting caught in endless + # recursion when the nkCall is semanticized + let oldExpr = a[lhsPos] + let newPragmas = copyExcept(pragmas, i) + if newPragmas.kind != nkEmpty and newPragmas.len == 0: + a[lhsPos] = oldExpr[namePos] + else: + a[lhsPos] = copyNode(oldExpr) + a[lhsPos].add(oldExpr[namePos]) + a[lhsPos].add(newPragmas) + + var unarySection = newNodeI(n.kind, a.info) + unarySection.add(a) + x.add(unarySection) + + # recursion assures that this works for multiple macro annotations too: + var r = semOverloadedCall(c, x, x, {skMacro, skTemplate}, {efNoUndeclared}) + if r == nil: + # Restore the old list of pragmas since we couldn't process this + a[lhsPos] = oldExpr + # No matching macro was found but there's always the possibility this may + # be a .pragma. template instead + continue + + doAssert r[0].kind == nkSym + let m = r[0].sym + case m.kind + of skMacro: result = semMacroExpr(c, r, r, m, {}) + of skTemplate: result = semTemplateExpr(c, r, m, {}) + else: + a[lhsPos] = oldExpr + continue + + doAssert result != nil + + return result + +template isLocalSym(sym: PSym): bool = + sym.kind in {skVar, skLet, skParam} and not + ({sfGlobal, sfPure} * sym.flags != {} or + sym.typ.kind == tyTypeDesc or + sfCompileTime in sym.flags) or + sym.kind in {skProc, skFunc, skIterator} and + sfGlobal notin sym.flags + +template isLocalVarSym(n: PNode): bool = + n.kind == nkSym and isLocalSym(n.sym) + +proc usesLocalVar(n: PNode): bool = + result = false + for z in 1 ..< n.len: + if n[z].isLocalVarSym: + return true + elif n[z].kind in nkCallKinds: + if usesLocalVar(n[z]): + return true + +proc globalVarInitCheck(c: PContext, n: PNode) = + if n.isLocalVarSym or n.kind in nkCallKinds and usesLocalVar(n): + localError(c.config, n.info, errCannotAssignToGlobal) + +const + errTupleUnpackingTupleExpected = "tuple expected for tuple unpacking, but got '$1'" + errTupleUnpackingDifferentLengths = "tuple with $1 elements expected, but got '$2' with $3 elements" + +proc makeVarTupleSection(c: PContext, n, a, def: PNode, typ: PType, symkind: TSymKind, origResult: var PNode): PNode = + ## expand tuple unpacking assignments into new var/let/const section + ## + ## mirrored with semexprs.makeTupleAssignments + if typ.kind != tyTuple: + localError(c.config, a.info, errTupleUnpackingTupleExpected % + [typeToString(typ, preferDesc)]) + elif a.len-2 != typ.len: + localError(c.config, a.info, errTupleUnpackingDifferentLengths % + [$(a.len-2), typeToString(typ, preferDesc), $typ.len]) + var + tempNode: PNode = nil + lastDef: PNode + let defkind = if symkind == skConst: nkConstDef else: nkIdentDefs + # temporary not needed if not const and RHS is tuple literal + # const breaks with seqs without temporary + let useTemp = def.kind notin {nkPar, nkTupleConstr} or symkind == skConst + if useTemp: + # use same symkind for compatibility with original section + let temp = newSym(symkind, getIdent(c.cache, "tmpTuple"), c.idgen, getCurrOwner(c), n.info) + temp.typ = typ + temp.flags.incl(sfGenSym) + lastDef = newNodeI(defkind, a.info) + newSons(lastDef, 3) + lastDef[0] = newSymNode(temp) + # NOTE: at the moment this is always ast.emptyNode, see parser.nim + lastDef[1] = a[^2] + lastDef[2] = def + temp.ast = lastDef + addToVarSection(c, origResult, n, lastDef) + tempNode = newSymNode(temp) + result = newNodeI(n.kind, a.info) + for j in 0..<a.len-2: + let name = a[j] + if useTemp and name.kind == nkIdent and name.ident.id == ord(wUnderscore): + # skip _ assignments if we are using a temp as they are already evaluated + continue + if name.kind == nkVarTuple: + # nested tuple + lastDef = newNodeI(nkVarTuple, name.info) + newSons(lastDef, name.len) + for k in 0..<name.len-2: + lastDef[k] = name[k] + else: + lastDef = newNodeI(defkind, name.info) + newSons(lastDef, 3) + lastDef[0] = name + lastDef[^2] = c.graph.emptyNode + if useTemp: + lastDef[^1] = newTupleAccessRaw(tempNode, j) + else: + var val = def[j] + if val.kind == nkExprColonExpr: val = val[1] + lastDef[^1] = val + result.add(lastDef) + +proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = + var b: PNode + result = copyNode(n) + + # transform var x, y = 12 into var x = 12; var y = 12 + # bug #18104; transformation should be finished before templates expansion + # TODO: move warnings for tuple here + var transformed = copyNode(n) + for i in 0..<n.len: + var a = n[i] + if a.kind == nkIdentDefs and a.len > 3 and a[^1].kind != nkEmpty: + for j in 0..<a.len-2: + var b = newNodeI(nkIdentDefs, a.info) + b.add a[j] + b.add a[^2] + b.add copyTree(a[^1]) + transformed.add b + else: + transformed.add a + let n = transformed + + for i in 0..<n.len: + var a = n[i] + if c.config.cmd == cmdIdeTools: suggestStmt(c, a) + if a.kind == nkCommentStmt: continue + if a.kind notin {nkIdentDefs, nkVarTuple}: illFormedAst(a, c.config) + checkMinSonsLen(a, 3, c.config) + + b = semVarMacroPragma(c, a, n) + if b != nil: + addToVarSection(c, result, b) + continue + + var hasUserSpecifiedType = false + var typ: PType = nil + if a[^2].kind != nkEmpty: + typ = semTypeNode(c, a[^2], nil) + hasUserSpecifiedType = true + + var typFlags: TTypeAllowedFlags = {} + + var def: PNode = c.graph.emptyNode + if typ != nil and typ.kind == tyRange and + c.graph.config.isDefined("nimPreviewRangeDefault") and + a[^1].kind == nkEmpty: + a[^1] = firstRange(c.config, typ) + + if a[^1].kind != nkEmpty: + def = semExprWithType(c, a[^1], {efTypeAllowed}, typ) + + if def.kind == nkSym and def.sym.kind in {skTemplate, skMacro}: + typFlags.incl taIsTemplateOrMacro + elif def.typ.kind == tyTypeDesc and c.p.owner.kind != skMacro: + typFlags.incl taProcContextIsNotMacro + + if typ != nil: + if typ.isMetaType: + def = inferWithMetatype(c, typ, def) + typ = def.typ + else: + # BUGFIX: ``fitNode`` is needed here! + # check type compatibility between def.typ and typ + def = fitNodeConsiderViewType(c, typ, def, def.info) + #changeType(def.skipConv, typ, check=true) + else: + typ = def.typ.skipTypes({tyStatic, tySink}).skipIntLit(c.idgen) + if typ.kind in tyUserTypeClasses and typ.isResolvedUserTypeClass: + typ = typ.last + if hasEmpty(typ): + localError(c.config, def.info, errCannotInferTypeOfTheLiteral % typ.kind.toHumanStr) + elif typ.kind == tyProc and def.kind == nkSym and isGenericRoutine(def.sym.ast): + let owner = typ.owner + let err = + # consistent error message with evaltempl/semMacroExpr + if owner != nil and owner.kind in {skTemplate, skMacro}: + errMissingGenericParamsForTemplate % def.renderTree + else: + errProcHasNoConcreteType % def.renderTree + localError(c.config, def.info, err) + when false: + # XXX This typing rule is neither documented nor complete enough to + # justify it. Instead use the newer 'unowned x' until we figured out + # a more general solution. + if symkind == skVar and typ.kind == tyOwned and def.kind notin nkCallKinds: + # special type inference rule: 'var it = ownedPointer' is turned + # into an unowned pointer. + typ = typ.lastSon + + # this can only happen for errornous var statements: + if typ == nil: continue + + if c.matchedConcept != nil: + typFlags.incl taConcept + typeAllowedCheck(c, a.info, typ, symkind, typFlags) + + var tup = skipTypes(typ, {tyGenericInst, tyAlias, tySink}) + if a.kind == nkVarTuple: + # generate new section from tuple unpacking and embed it into this one + let assignments = makeVarTupleSection(c, n, a, def, tup, symkind, result) + let resSection = semVarOrLet(c, assignments, symkind) + for resDef in resSection: + addToVarSection(c, result, n, resDef) + else: + if tup.kind == tyTuple and def.kind in {nkPar, nkTupleConstr} and + a.len > 3: + # var a, b = (1, 2) + message(c.config, a.info, warnEachIdentIsTuple) + + for j in 0..<a.len-2: + if a[j].kind == nkDotExpr: + fillPartialObject(c, a[j], typ) + addToVarSection(c, result, n, a) + continue + var v = semIdentDef(c, a[j], symkind, false) + when defined(nimsuggest): + v.hasUserSpecifiedType = hasUserSpecifiedType + styleCheckDef(c, v) + onDef(a[j].info, v) + if sfGenSym notin v.flags: + if not isDiscardUnderscore(v): addInterfaceDecl(c, v) + else: + if v.owner == nil: v.owner = c.p.owner + when oKeepVariableNames: + if c.inUnrolledContext > 0: v.flags.incl(sfShadowed) + else: + let shadowed = findShadowedVar(c, v) + if shadowed != nil: + shadowed.flags.incl(sfShadowed) + if shadowed.kind == skResult and sfGenSym notin v.flags: + message(c.config, a.info, warnResultShadowed) + if def.kind != nkEmpty: + if sfThread in v.flags: localError(c.config, def.info, errThreadvarCannotInit) + setVarType(c, v, typ) + # this is needed for the evaluation pass, guard checking + # and custom pragmas: + b = newNodeI(nkIdentDefs, a.info) + if importantComments(c.config): + # keep documentation information: + b.comment = a.comment + # postfix not generated here (to generate, get rid of it in transf) + if a[j].kind == nkPragmaExpr: + var p = newNodeI(nkPragmaExpr, a.info) + p.add newSymNode(v) + p.add a[j][1] + b.add p + else: + b.add newSymNode(v) + # keep type desc for doc generator + b.add a[^2] + b.add copyTree(def) + addToVarSection(c, result, n, b) + v.ast = b + if def.kind == nkEmpty: + let actualType = v.typ.skipTypes({tyGenericInst, tyAlias, + tyUserTypeClassInst}) + if actualType.kind in {tyObject, tyDistinct} and + actualType.requiresInit: + defaultConstructionError(c, v.typ, v.info) + else: + checkNilable(c, v) + # allow let to not be initialised if imported from C: + if v.kind == skLet and sfImportc notin v.flags and (strictDefs notin c.features or not isLocalSym(v)): + localError(c.config, a.info, errLetNeedsInit) + if sfCompileTime in v.flags: + var x = newNodeI(result.kind, v.info) + x.add result[i] + vm.setupCompileTimeVar(c.module, c.idgen, c.graph, x) + if v.flags * {sfGlobal, sfThread} == {sfGlobal}: + message(c.config, v.info, hintGlobalVar) + if {sfGlobal, sfPure} <= v.flags: + globalVarInitCheck(c, def) + suggestSym(c.graph, v.info, v, c.graph.usageSym) + +proc semConst(c: PContext, n: PNode): PNode = + result = copyNode(n) + inc c.inStaticContext + var b: PNode + for i in 0..<n.len: + var a = n[i] + if c.config.cmd == cmdIdeTools: suggestStmt(c, a) + if a.kind == nkCommentStmt: continue + if a.kind notin {nkConstDef, nkVarTuple}: illFormedAst(a, c.config) + checkMinSonsLen(a, 3, c.config) + + b = semVarMacroPragma(c, a, n) + if b != nil: + addToVarSection(c, result, b) + continue + + var hasUserSpecifiedType = false + var typ: PType = nil + if a[^2].kind != nkEmpty: + typ = semTypeNode(c, a[^2], nil) + hasUserSpecifiedType = true + + var typFlags: TTypeAllowedFlags = {} + + # don't evaluate here since the type compatibility check below may add a converter + openScope(c) + var def = semExprWithType(c, a[^1], {efTypeAllowed}, typ) + + if def.kind == nkSym and def.sym.kind in {skTemplate, skMacro}: + typFlags.incl taIsTemplateOrMacro + elif def.typ.kind == tyTypeDesc and c.p.owner.kind != skMacro: + typFlags.incl taProcContextIsNotMacro + + # check type compatibility between def.typ and typ: + if typ != nil: + if typ.isMetaType: + def = inferWithMetatype(c, typ, def) + typ = def.typ + else: + def = fitRemoveHiddenConv(c, typ, def) + else: + typ = def.typ + + # evaluate the node + def = semConstExpr(c, def) + if def == nil: + localError(c.config, a[^1].info, errConstExprExpected) + continue + if def.kind != nkNilLit: + if c.matchedConcept != nil: + typFlags.incl taConcept + typeAllowedCheck(c, a.info, typ, skConst, typFlags) + closeScope(c) + + if a.kind == nkVarTuple: + # generate new section from tuple unpacking and embed it into this one + let assignments = makeVarTupleSection(c, n, a, def, typ, skConst, result) + let resSection = semConst(c, assignments) + for resDef in resSection: + addToVarSection(c, result, n, resDef) + else: + for j in 0..<a.len-2: + var v = semIdentDef(c, a[j], skConst) + when defined(nimsuggest): + v.hasUserSpecifiedType = hasUserSpecifiedType + if sfGenSym notin v.flags: addInterfaceDecl(c, v) + elif v.owner == nil: v.owner = getCurrOwner(c) + styleCheckDef(c, v) + onDef(a[j].info, v) + + var fillSymbol = true + if v.typ != nil: + # symbol already has type and probably value + # don't mutate + fillSymbol = false + else: + setVarType(c, v, typ) + b = newNodeI(nkConstDef, a.info) + if importantComments(c.config): b.comment = a.comment + # postfix not generated here (to generate, get rid of it in transf) + if a[j].kind == nkPragmaExpr: + var p = newNodeI(nkPragmaExpr, a.info) + p.add newSymNode(v) + p.add a[j][1].copyTree + b.add p + else: + b.add newSymNode(v) + b.add a[1] + b.add copyTree(def) + if fillSymbol: + v.ast = b + addToVarSection(c, result, n, b) + dec c.inStaticContext + +include semfields + + +proc symForVar(c: PContext, n: PNode): PSym = + let m = if n.kind == nkPragmaExpr: n[0] else: n + result = newSymG(skForVar, m, c) + styleCheckDef(c, result) + onDef(n.info, result) + if n.kind == nkPragmaExpr: + pragma(c, result, n[1], forVarPragmas) + +proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode = + result = n + let iterBase = n[^2].typ + var iter = skipTypes(iterBase, {tyGenericInst, tyAlias, tySink, tyOwned}) + var iterAfterVarLent = iter.skipTypes({tyGenericInst, tyAlias, tyLent, tyVar}) + # n.len == 3 means that there is one for loop variable + # and thus no tuple unpacking: + if iterAfterVarLent.kind == tyEmpty: + localError(c.config, n[^2].info, "cannot infer element type of $1" % + renderTree(n[^2], {renderNoComments})) + if iterAfterVarLent.kind != tyTuple or n.len == 3: + if n.len == 3: + if n[0].kind == nkVarTuple: + if iterAfterVarLent.kind != tyTuple: + return localErrorNode(c, n, n[0].info, errTupleUnpackingTupleExpected % + [typeToString(n[1].typ, preferDesc)]) + elif n[0].len-1 != iterAfterVarLent.len: + return localErrorNode(c, n, n[0].info, errWrongNumberOfVariables) + + for i in 0..<n[0].len-1: + var v = symForVar(c, n[0][i]) + if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal) + case iter.kind + of tyVar, tyLent: + v.typ = newTypeS(iter.kind, c) + v.typ.add iterAfterVarLent[i] + if tfVarIsPtr in iter.flags: + v.typ.flags.incl tfVarIsPtr + else: + v.typ = iter[i] + n[0][i] = newSymNode(v) + if sfGenSym notin v.flags and not isDiscardUnderscore(v): addDecl(c, v) + elif v.owner == nil: v.owner = getCurrOwner(c) + else: + var v = symForVar(c, n[0]) + if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal) + # BUGFIX: don't use `iter` here as that would strip away + # the ``tyGenericInst``! See ``tests/compile/tgeneric.nim`` + # for an example: + v.typ = iterBase + n[0] = newSymNode(v) + if sfGenSym notin v.flags and not isDiscardUnderscore(v): addDecl(c, v) + elif v.owner == nil: v.owner = getCurrOwner(c) + else: + localError(c.config, n.info, errWrongNumberOfVariables) + elif n.len-2 != iterAfterVarLent.len: + localError(c.config, n.info, errWrongNumberOfVariables) + else: + for i in 0..<n.len - 2: + if n[i].kind == nkVarTuple: + var mutable = false + var isLent = false + case iter[i].kind + of tyVar: + mutable = true + iter[i] = iter[i].skipTypes({tyVar}) + of tyLent: + isLent = true + iter[i] = iter[i].skipTypes({tyLent}) + else: discard + + if n[i].len-1 != iter[i].len: + localError(c.config, n[i].info, errWrongNumberOfVariables) + for j in 0..<n[i].len-1: + var v = symForVar(c, n[i][j]) + if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal) + if mutable: + v.typ = newTypeS(tyVar, c) + v.typ.add iter[i][j] + elif isLent: + v.typ = newTypeS(tyLent, c) + v.typ.add iter[i][j] + else: + v.typ = iter[i][j] + n[i][j] = newSymNode(v) + if not isDiscardUnderscore(v): addDecl(c, v) + elif v.owner == nil: v.owner = getCurrOwner(c) + else: + var v = symForVar(c, n[i]) + if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal) + case iter.kind + of tyVar, tyLent: + v.typ = newTypeS(iter.kind, c) + v.typ.add iterAfterVarLent[i] + if tfVarIsPtr in iter.flags: + v.typ.flags.incl tfVarIsPtr + else: + v.typ = iter[i] + n[i] = newSymNode(v) + if sfGenSym notin v.flags: + if not isDiscardUnderscore(v): addDecl(c, v) + elif v.owner == nil: v.owner = getCurrOwner(c) + inc(c.p.nestedLoopCounter) + let oldBreakInLoop = c.p.breakInLoop + c.p.breakInLoop = true + openScope(c) + n[^1] = semExprBranch(c, n[^1], flags) + if efInTypeof notin flags: + discardCheck(c, n[^1], flags) + closeScope(c) + c.p.breakInLoop = oldBreakInLoop + dec(c.p.nestedLoopCounter) + +proc implicitIterator(c: PContext, it: string, arg: PNode): PNode = + result = newNodeI(nkCall, arg.info) + result.add(newIdentNode(getIdent(c.cache, it), arg.info)) + if arg.typ != nil and arg.typ.kind in {tyVar, tyLent}: + result.add newDeref(arg) + else: + result.add arg + result = semExprNoDeref(c, result, {efWantIterator}) + +proc isTrivalStmtExpr(n: PNode): bool = + for i in 0..<n.len-1: + if n[i].kind notin {nkEmpty, nkCommentStmt}: + return false + result = true + +proc handleStmtMacro(c: PContext; n, selector: PNode; magicType: string; + flags: TExprFlags): PNode = + if selector.kind in nkCallKinds: + # we transform + # n := for a, b, c in m(x, y, z): Y + # to + # m(n) + let maType = magicsys.getCompilerProc(c.graph, magicType) + if maType == nil: return + + let headSymbol = selector[0] + var o: TOverloadIter = default(TOverloadIter) + var match: PSym = nil + var symx = initOverloadIter(o, c, headSymbol) + while symx != nil: + if symx.kind in {skTemplate, skMacro}: + if symx.typ.len == 2 and symx.typ.firstParamType == maType.typ: + if match == nil: + match = symx + else: + localError(c.config, n.info, errAmbiguousCallXYZ % [ + getProcHeader(c.config, match), + getProcHeader(c.config, symx), $selector]) + symx = nextOverloadIter(o, c, headSymbol) + + if match == nil: return + var callExpr = newNodeI(nkCall, n.info) + callExpr.add newSymNode(match) + callExpr.add n + case match.kind + of skMacro: result = semMacroExpr(c, callExpr, callExpr, match, flags) + of skTemplate: result = semTemplateExpr(c, callExpr, match, flags) + else: result = nil + else: + result = nil + +proc handleForLoopMacro(c: PContext; n: PNode; flags: TExprFlags): PNode = + result = handleStmtMacro(c, n, n[^2], "ForLoopStmt", flags) + +proc handleCaseStmtMacro(c: PContext; n: PNode; flags: TExprFlags): PNode = + # n[0] has been sem'checked and has a type. We use this to resolve + # '`case`(n[0])' but then we pass 'n' to the `case` macro. This seems to + # be the best solution. + var toResolve = newNodeI(nkCall, n.info) + toResolve.add newIdentNode(getIdent(c.cache, "case"), n.info) + toResolve.add n[0] + + var errors: CandidateErrors = @[] + var r = resolveOverloads(c, toResolve, toResolve, {skTemplate, skMacro}, {efNoUndeclared}, + errors, false) + if r.state == csMatch: + var match = r.calleeSym + markUsed(c, n[0].info, match) + onUse(n[0].info, match) + + # but pass 'n' to the `case` macro, not 'n[0]': + r.call[1] = n + let toExpand = semResolvedCall(c, r, r.call, {}) + case match.kind + of skMacro: result = semMacroExpr(c, toExpand, toExpand, match, flags) + of skTemplate: result = semTemplateExpr(c, toExpand, match, flags) + else: result = errorNode(c, n[0]) + else: + result = errorNode(c, n[0]) + if result.kind == nkEmpty: + localError(c.config, n[0].info, errSelectorMustBeOfCertainTypes) + # this would be the perfectly consistent solution with 'for loop macros', + # but it kinda sucks for pattern matching as the matcher is not attached to + # a type then: + when false: + result = handleStmtMacro(c, n, n[0], "CaseStmt") + +proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode = + checkMinSonsLen(n, 3, c.config) + result = handleForLoopMacro(c, n, flags) + if result != nil: return result + openScope(c) + result = n + n[^2] = semExprNoDeref(c, n[^2], {efWantIterator}) + var call = n[^2] + + if call.kind == nkStmtListExpr and (isTrivalStmtExpr(call) or (call.lastSon.kind in nkCallKinds and call.lastSon[0].sym.kind == skIterator)): + call = call.lastSon + n[^2] = call + let isCallExpr = call.kind in nkCallKinds + if isCallExpr and call[0].kind == nkSym and + call[0].sym.magic in {mFields, mFieldPairs, mOmpParFor}: + if call[0].sym.magic == mOmpParFor: + result = semForVars(c, n, flags) + result.transitionSonsKind(nkParForStmt) + else: + result = semForFields(c, n, call[0].sym.magic) + elif isCallExpr and isClosureIterator(call[0].typ.skipTypes(abstractInst)): + # first class iterator: + result = semForVars(c, n, flags) + elif not isCallExpr or call[0].kind != nkSym or + call[0].sym.kind != skIterator: + if n.len == 3: + n[^2] = implicitIterator(c, "items", n[^2]) + elif n.len == 4: + n[^2] = implicitIterator(c, "pairs", n[^2]) + else: + localError(c.config, n[^2].info, "iterator within for loop context expected") + result = semForVars(c, n, flags) + else: + result = semForVars(c, n, flags) + # propagate any enforced VoidContext: + if n[^1].typ == c.enforceVoidContext: + result.typ = c.enforceVoidContext + elif efInTypeof in flags: + result.typ = result.lastSon.typ + closeScope(c) + +proc semCase(c: PContext, n: PNode; flags: TExprFlags; expectedType: PType = nil): PNode = + result = n + checkMinSonsLen(n, 2, c.config) + openScope(c) + pushCaseContext(c, n) + n[0] = semExprWithType(c, n[0]) + var covered: Int128 = toInt128(0) + var typ = commonTypeBegin + var expectedType = expectedType + var hasElse = false + let caseTyp = skipTypes(n[0].typ, abstractVar-{tyTypeDesc}) + var chckCovered = caseTyp.shouldCheckCaseCovered() + case caseTyp.kind + of tyFloat..tyFloat128, tyString, tyCstring, tyError, shouldChckCovered, tyRange: + discard + else: + popCaseContext(c) + closeScope(c) + return handleCaseStmtMacro(c, n, flags) + template invalidOrderOfBranches(n: PNode) = + localError(c.config, n.info, "invalid order of case branches") + break + + for i in 1..<n.len: + setCaseContextIdx(c, i) + var x = n[i] + when defined(nimsuggest): + if c.config.ideCmd == ideSug and exactEquals(c.config.m.trackPos, x.info) and caseTyp.kind == tyEnum: + suggestEnum(c, x, caseTyp) + case x.kind + of nkOfBranch: + if hasElse: invalidOrderOfBranches(x) + checkMinSonsLen(x, 2, c.config) + semCaseBranch(c, n, x, i, covered) + var last = x.len-1 + x[last] = semExprBranchScope(c, x[last], expectedType) + typ = commonType(c, typ, x[last]) + if not endsInNoReturn(x[last]): + expectedType = typ + of nkElifBranch: + if hasElse: invalidOrderOfBranches(x) + chckCovered = false + checkSonsLen(x, 2, c.config) + openScope(c) + x[0] = forceBool(c, semExprWithType(c, x[0], expectedType = getSysType(c.graph, n.info, tyBool))) + x[1] = semExprBranch(c, x[1], expectedType = expectedType) + typ = commonType(c, typ, x[1]) + if not endsInNoReturn(x[1]): + expectedType = typ + closeScope(c) + of nkElse: + checkSonsLen(x, 1, c.config) + x[0] = semExprBranchScope(c, x[0], expectedType) + typ = commonType(c, typ, x[0]) + if not endsInNoReturn(x[0]): + expectedType = typ + if (chckCovered and covered == toCover(c, n[0].typ)) or hasElse: + message(c.config, x.info, warnUnreachableElse) + hasElse = true + chckCovered = false + else: + illFormedAst(x, c.config) + if chckCovered: + if covered == toCover(c, n[0].typ): + hasElse = true + elif n[0].typ.skipTypes(abstractRange).kind in {tyEnum, tyChar}: + localError(c.config, n.info, "not all cases are covered; missing: $1" % + formatMissingEnums(c, n)) + else: + localError(c.config, n.info, "not all cases are covered") + popCaseContext(c) + closeScope(c) + if isEmptyType(typ) or typ.kind in {tyNil, tyUntyped} or + (not hasElse and efInTypeof notin flags): + for i in 1..<n.len: discardCheck(c, n[i].lastSon, flags) + # propagate any enforced VoidContext: + if typ == c.enforceVoidContext: + result.typ = c.enforceVoidContext + else: + for i in 1..<n.len: + var it = n[i] + let j = it.len-1 + if not endsInNoReturn(it[j]): + it[j] = fitNode(c, typ, it[j], it[j].info) + result.typ = typ + +proc semRaise(c: PContext, n: PNode): PNode = + result = n + checkSonsLen(n, 1, c.config) + if n[0].kind != nkEmpty: + n[0] = semExprWithType(c, n[0]) + var typ = n[0].typ + if not isImportedException(typ, c.config): + typ = typ.skipTypes({tyAlias, tyGenericInst, tyOwned}) + if typ.kind != tyRef: + localError(c.config, n.info, errExprCannotBeRaised) + if typ.len > 0 and not isException(typ.elementType): + localError(c.config, n.info, "raised object of type $1 does not inherit from Exception" % typeToString(typ)) + +proc addGenericParamListToScope(c: PContext, n: PNode) = + if n.kind != nkGenericParams: illFormedAst(n, c.config) + for i in 0..<n.len: + var a = n[i] + if a.kind == nkSym: addDecl(c, a.sym) + else: illFormedAst(a, c.config) + +proc typeSectionTypeName(c: PContext; n: PNode): PNode = + if n.kind == nkPragmaExpr: + if n.len == 0: illFormedAst(n, c.config) + result = n[0] + else: + result = n + if result.kind == nkPostfix: + if result.len != 2: illFormedAst(n, c.config) + result = result[1] + if result.kind != nkSym: illFormedAst(n, c.config) + +proc typeDefLeftSidePass(c: PContext, typeSection: PNode, i: int) = + let typeDef = typeSection[i] + checkSonsLen(typeDef, 3, c.config) + var name = typeDef[0] + var s: PSym = nil + if name.kind == nkDotExpr and typeDef[2].kind == nkObjectTy: + let pkgName = considerQuotedIdent(c, name[0]) + let typName = considerQuotedIdent(c, name[1]) + let pkg = c.graph.packageSyms.strTableGet(pkgName) + if pkg.isNil or pkg.kind != skPackage: + localError(c.config, name.info, "unknown package name: " & pkgName.s) + else: + let typsym = c.graph.packageTypes.strTableGet(typName) + if typsym.isNil: + s = semIdentDef(c, name[1], skType) + onDef(name[1].info, s) + s.typ = newTypeS(tyObject, c) + s.typ.sym = s + s.flags.incl sfForward + c.graph.packageTypes.strTableAdd s + addInterfaceDecl(c, s) + elif typsym.kind == skType and sfForward in typsym.flags: + s = typsym + addInterfaceDecl(c, s) + # PRTEMP no onDef here? + else: + localError(c.config, name.info, typsym.name.s & " is not a type that can be forwarded") + s = typsym + else: + s = semIdentDef(c, name, skType) + onDef(name.info, s) + s.typ = newTypeS(tyForward, c) + s.typ.sym = s # process pragmas: + if name.kind == nkPragmaExpr: + let rewritten = applyTypeSectionPragmas(c, name[1], typeDef) + if rewritten != nil: + case rewritten.kind + of nkTypeDef: + typeSection[i] = rewritten + of nkTypeSection: + typeSection.sons[i .. i] = rewritten.sons + else: illFormedAst(rewritten, c.config) + typeDefLeftSidePass(c, typeSection, i) + return + pragma(c, s, name[1], typePragmas) + if sfForward in s.flags: + # check if the symbol already exists: + let pkg = c.module.owner + if not isTopLevel(c) or pkg.isNil: + localError(c.config, name.info, "only top level types in a package can be 'package'") + else: + let typsym = c.graph.packageTypes.strTableGet(s.name) + if typsym != nil: + if sfForward notin typsym.flags or sfNoForward notin typsym.flags: + typeCompleted(typsym) + typsym.info = s.info + else: + localError(c.config, name.info, "cannot complete type '" & s.name.s & "' twice; " & + "previous type completion was here: " & c.config$typsym.info) + s = typsym + # add it here, so that recursive types are possible: + if sfGenSym notin s.flags: addInterfaceDecl(c, s) + elif s.owner == nil: s.owner = getCurrOwner(c) + + if name.kind == nkPragmaExpr: + if name[0].kind == nkPostfix: + typeDef[0][0][1] = newSymNode(s) + else: + typeDef[0][0] = newSymNode(s) + else: + if name.kind == nkPostfix: + typeDef[0][1] = newSymNode(s) + else: + typeDef[0] = newSymNode(s) + +proc typeSectionLeftSidePass(c: PContext, n: PNode) = + # process the symbols on the left side for the whole type section, before + # we even look at the type definitions on the right + var i = 0 + while i < n.len: # n may grow due to type pragma macros + var a = n[i] + when defined(nimsuggest): + if c.config.cmd == cmdIdeTools: + inc c.inTypeContext + suggestStmt(c, a) + dec c.inTypeContext + case a.kind + of nkCommentStmt: discard + of nkTypeDef: typeDefLeftSidePass(c, n, i) + else: illFormedAst(a, c.config) + inc i + +proc checkCovariantParamsUsages(c: PContext; genericType: PType) = + var body = genericType.typeBodyImpl + + proc traverseSubTypes(c: PContext; t: PType): bool = + template error(msg) = localError(c.config, genericType.sym.info, msg) + result = false + template subresult(r) = + let sub = r + result = result or sub + + case t.kind + of tyGenericParam: + t.flags.incl tfWeakCovariant + return true + of tyObject: + for field in t.n: + subresult traverseSubTypes(c, field.typ) + of tyArray: + return traverseSubTypes(c, t.elementType) + of tyProc: + for subType in t.signature: + if subType != nil: + subresult traverseSubTypes(c, subType) + if result: + error("non-invariant type param used in a proc type: " & $t) + of tySequence: + return traverseSubTypes(c, t.elementType) + of tyGenericInvocation: + let targetBody = t.genericHead + for i in 1..<t.len: + let param = t[i] + if param.kind == tyGenericParam: + if tfCovariant in param.flags: + let formalFlags = targetBody[i-1].flags + if tfCovariant notin formalFlags: + error("covariant param '" & param.sym.name.s & + "' used in a non-covariant position") + elif tfWeakCovariant in formalFlags: + param.flags.incl tfWeakCovariant + result = true + elif tfContravariant in param.flags: + let formalParam = targetBody[i-1].sym + if tfContravariant notin formalParam.typ.flags: + error("contravariant param '" & param.sym.name.s & + "' used in a non-contravariant position") + result = true + else: + subresult traverseSubTypes(c, param) + of tyAnd, tyOr, tyNot, tyStatic, tyBuiltInTypeClass, tyCompositeTypeClass: + error("non-invariant type parameters cannot be used with types such '" & $t & "'") + of tyUserTypeClass, tyUserTypeClassInst: + error("non-invariant type parameters are not supported in concepts") + of tyTuple: + for fieldType in t.kids: + subresult traverseSubTypes(c, fieldType) + of tyPtr, tyRef, tyVar, tyLent: + if t.elementType.kind == tyGenericParam: return true + return traverseSubTypes(c, t.elementType) + of tyDistinct, tyAlias, tySink, tyOwned: + return traverseSubTypes(c, t.skipModifier) + of tyGenericInst: + internalAssert c.config, false + else: + discard + discard traverseSubTypes(c, body) + +proc typeSectionRightSidePass(c: PContext, n: PNode) = + for i in 0..<n.len: + var a = n[i] + if a.kind == nkCommentStmt: continue + if a.kind != nkTypeDef: illFormedAst(a, c.config) + checkSonsLen(a, 3, c.config) + let name = typeSectionTypeName(c, a[0]) + var s = name.sym + if s.magic == mNone and a[2].kind == nkEmpty: + localError(c.config, a.info, errImplOfXexpected % s.name.s) + if s.magic != mNone: processMagicType(c, s) + let oldFlags = s.typ.flags + if a[1].kind != nkEmpty: + # We have a generic type declaration here. In generic types, + # symbol lookup needs to be done here. + openScope(c) + pushOwner(c, s) + if s.magic == mNone: s.typ.kind = tyGenericBody + # XXX for generic type aliases this is not correct! We need the + # underlying Id really: + # + # type + # TGObj[T] = object + # TAlias[T] = TGObj[T] + # + s.typ.n = semGenericParamList(c, a[1], s.typ) + a[1] = s.typ.n + s.typ.size = -1 # could not be computed properly + # we fill it out later. For magic generics like 'seq', it won't be filled + # so we use tyNone instead of nil to not crash for strange conversions + # like: mydata.seq + if s.typ.kind in {tyOpenArray, tyVarargs} and s.typ.len == 1: + # XXX investigate why `tySequence` cannot be added here for now. + discard + else: + rawAddSon(s.typ, newTypeS(tyNone, c)) + s.ast = a + inc c.inGenericContext + var body = semTypeNode(c, a[2], s.typ) + dec c.inGenericContext + if body != nil: + body.sym = s + body.size = -1 # could not be computed properly + if body.kind == tyObject: + # add flags applied to generic type to object (nominal) type + incl(body.flags, oldFlags) + # {.inheritable, final.} is already disallowed, but + # object might have been assumed to be final + if tfInheritable in oldFlags and tfFinal in body.flags: + excl(body.flags, tfFinal) + s.typ[^1] = body + if tfCovariant in s.typ.flags: + checkCovariantParamsUsages(c, s.typ) + # XXX: This is a temporary limitation: + # The codegen currently produces various failures with + # generic imported types that have fields, but we need + # the fields specified in order to detect weak covariance. + # The proper solution is to teach the codegen how to handle + # such types, because this would offer various interesting + # possibilities such as instantiating C++ generic types with + # garbage collected Nim types. + if sfImportc in s.flags: + var body = s.typ.last + if body.kind == tyObject: + # erases all declared fields + body.n.sons = @[] + + popOwner(c) + closeScope(c) + elif a[2].kind != nkEmpty: + # process the type's body: + pushOwner(c, s) + var t = semTypeNode(c, a[2], s.typ) + if s.typ == nil: + s.typ = t + elif t != s.typ and (s.typ == nil or s.typ.kind != tyAlias): + # this can happen for e.g. tcan_alias_specialised_generic: + assignType(s.typ, t) + #debug s.typ + s.ast = a + popOwner(c) + # If the right hand side expression was a macro call we replace it with + # its evaluated result here so that we don't execute it once again in the + # final pass + if a[2].kind in nkCallKinds: + incl a[2].flags, nfSem # bug #10548 + if sfExportc in s.flags: + if s.typ.kind == tyAlias: + localError(c.config, name.info, "{.exportc.} not allowed for type aliases") + elif s.typ.kind == tyGenericBody: + localError(c.config, name.info, "{.exportc.} not allowed for generic types") + + if tfBorrowDot in s.typ.flags: + let body = s.typ.skipTypes({tyGenericBody}) + if body.kind != tyDistinct: + # flag might be copied from alias/instantiation: + let t = body.skipTypes({tyAlias, tyGenericInst}) + if not (t.kind == tyDistinct and tfBorrowDot in t.flags): + excl s.typ.flags, tfBorrowDot + localError(c.config, name.info, "only a 'distinct' type can borrow `.`") + let aa = a[2] + if aa.kind in {nkRefTy, nkPtrTy} and aa.len == 1 and + aa[0].kind == nkObjectTy: + # give anonymous object a dummy symbol: + var st = s.typ + if st.kind == tyGenericBody: st = st.typeBodyImpl + internalAssert c.config, st.kind in {tyPtr, tyRef} + internalAssert c.config, st.last.sym == nil + incl st.flags, tfRefsAnonObj + let objTy = st.last + # add flags for `ref object` etc to underlying `object` + incl(objTy.flags, oldFlags) + # {.inheritable, final.} is already disallowed, but + # object might have been assumed to be final + if tfInheritable in oldFlags and tfFinal in objTy.flags: + excl(objTy.flags, tfFinal) + let obj = newSym(skType, getIdent(c.cache, s.name.s & ":ObjectType"), + c.idgen, getCurrOwner(c), s.info) + obj.flags.incl sfGeneratedType + let symNode = newSymNode(obj) + obj.ast = a.shallowCopy + case a[0].kind + of nkSym: obj.ast[0] = symNode + of nkPragmaExpr: + obj.ast[0] = a[0].shallowCopy + if a[0][0].kind == nkPostfix: + obj.ast[0][0] = a[0][0].shallowCopy + obj.ast[0][0][1] = symNode + else: + obj.ast[0][0] = symNode + obj.ast[0][1] = a[0][1] + of nkPostfix: + obj.ast[0] = a[0].shallowCopy + obj.ast[0][1] = symNode + else: assert(false) + obj.ast[1] = a[1] + obj.ast[2] = a[2][0] + if sfPure in s.flags: + obj.flags.incl sfPure + obj.typ = objTy + objTy.sym = obj + for sk in c.skipTypes: + discard semTypeNode(c, sk, nil) + c.skipTypes = @[] + +proc checkForMetaFields(c: PContext; n: PNode; hasError: var bool) = + proc checkMeta(c: PContext; n: PNode; t: PType; hasError: var bool; parent: PType) = + if t != nil and (t.isMetaType or t.kind == tyNone) and tfGenericTypeParam notin t.flags: + if t.kind == tyBuiltInTypeClass and t.len == 1 and t.elementType.kind == tyProc: + localError(c.config, n.info, ("'$1' is not a concrete type; " & + "for a callback without parameters use 'proc()'") % t.typeToString) + elif t.kind == tyNone and parent != nil: + # TODO: openarray has the `tfGenericTypeParam` flag & generics + # TODO: handle special cases (sink etc.) and views + localError(c.config, n.info, errTIsNotAConcreteType % parent.typeToString) + else: + localError(c.config, n.info, errTIsNotAConcreteType % t.typeToString) + hasError = true + + if n.isNil: return + case n.kind + of nkRecList, nkRecCase: + for s in n: checkForMetaFields(c, s, hasError) + of nkOfBranch, nkElse: + checkForMetaFields(c, n.lastSon, hasError) + of nkSym: + let t = n.sym.typ + case t.kind + of tySequence, tySet, tyArray, tyOpenArray, tyVar, tyLent, tyPtr, tyRef, + tyProc, tyGenericInvocation, tyGenericInst, tyAlias, tySink, tyOwned: + let start = ord(t.kind in {tyGenericInvocation, tyGenericInst}) + for i in start..<t.len: + checkMeta(c, n, t[i], hasError, t) + else: + checkMeta(c, n, t, hasError, nil) + else: + internalAssert c.config, false + +proc typeSectionFinalPass(c: PContext, n: PNode) = + for i in 0..<n.len: + var a = n[i] + if a.kind == nkCommentStmt: continue + let name = typeSectionTypeName(c, a[0]) + var s = name.sym + # check the style here after the pragmas have been processed: + styleCheckDef(c, s) + # compute the type's size and check for illegal recursions: + if a[1].kind == nkEmpty: + var x = a[2] + if x.kind in nkCallKinds and nfSem in x.flags: + discard "already semchecked, see line marked with bug #10548" + else: + while x.kind in {nkStmtList, nkStmtListExpr} and x.len > 0: + x = x.lastSon + # we need the 'safeSkipTypes' here because illegally recursive types + # can enter at this point, see bug #13763 + if x.kind notin {nkObjectTy, nkDistinctTy, nkEnumTy, nkEmpty} and + s.typ.safeSkipTypes(abstractPtrs).kind notin {tyObject, tyEnum}: + # type aliases are hard: + var t = semTypeNode(c, x, nil) + assert t != nil + if s.typ != nil and s.typ.kind notin {tyAlias, tySink}: + if t.kind in {tyProc, tyGenericInst} and not t.isMetaType: + assignType(s.typ, t) + s.typ.itemId = t.itemId + elif t.kind in {tyObject, tyEnum, tyDistinct}: + assert s.typ != nil + assignType(s.typ, t) + s.typ.itemId = t.itemId # same id + var hasError = false + let baseType = s.typ.safeSkipTypes(abstractPtrs) + if baseType.kind in {tyObject, tyTuple} and not baseType.n.isNil and + (x.kind in {nkObjectTy, nkTupleTy} or + (x.kind in {nkRefTy, nkPtrTy} and x.len == 1 and + x[0].kind in {nkObjectTy, nkTupleTy}) + ): + checkForMetaFields(c, baseType.n, hasError) + if not hasError: + checkConstructedType(c.config, s.info, s.typ) + #instAllTypeBoundOp(c, n.info) + + +proc semAllTypeSections(c: PContext; n: PNode): PNode = + proc gatherStmts(c: PContext; n: PNode; result: PNode) {.nimcall.} = + case n.kind + of nkIncludeStmt: + for i in 0..<n.len: + var f = checkModuleName(c.config, n[i]) + if f != InvalidFileIdx: + if containsOrIncl(c.includedFiles, f.int): + localError(c.config, n.info, errRecursiveDependencyX % toMsgFilename(c.config, f)) + else: + let code = c.graph.includeFileCallback(c.graph, c.module, f) + gatherStmts c, code, result + excl(c.includedFiles, f.int) + of nkStmtList: + for i in 0..<n.len: + gatherStmts(c, n[i], result) + of nkTypeSection: + incl n.flags, nfSem + typeSectionLeftSidePass(c, n) + result.add n + else: + result.add n + + result = newNodeI(nkStmtList, n.info) + gatherStmts(c, n, result) + + template rec(name) = + for i in 0..<result.len: + if result[i].kind == nkTypeSection: + name(c, result[i]) + + rec typeSectionRightSidePass + rec typeSectionFinalPass + when false: + # too beautiful to delete: + template rec(name; setbit=false) = + proc `name rec`(c: PContext; n: PNode) {.nimcall.} = + if n.kind == nkTypeSection: + when setbit: incl n.flags, nfSem + name(c, n) + elif n.kind == nkStmtList: + for i in 0..<n.len: + `name rec`(c, n[i]) + `name rec`(c, n) + rec typeSectionLeftSidePass, true + rec typeSectionRightSidePass + rec typeSectionFinalPass + +proc semTypeSection(c: PContext, n: PNode): PNode = + ## Processes a type section. This must be done in separate passes, in order + ## to allow the type definitions in the section to reference each other + ## without regard for the order of their definitions. + if sfNoForward notin c.module.flags or nfSem notin n.flags: + inc c.inTypeContext + typeSectionLeftSidePass(c, n) + typeSectionRightSidePass(c, n) + typeSectionFinalPass(c, n) + dec c.inTypeContext + result = n + +proc semParamList(c: PContext, n, genericParams: PNode, s: PSym) = + s.typ = semProcTypeNode(c, n, genericParams, nil, s.kind) + +proc addParams(c: PContext, n: PNode, kind: TSymKind) = + for i in 1..<n.len: + if n[i].kind == nkSym: addParamOrResult(c, n[i].sym, kind) + else: illFormedAst(n, c.config) + +proc semBorrow(c: PContext, n: PNode, s: PSym) = + # search for the correct alias: + var (b, state) = searchForBorrowProc(c, c.currentScope.parent, s) + case state + of bsMatch: + # store the alias: + n[bodyPos] = newSymNode(b) + # Carry over the original symbol magic, this is necessary in order to ensure + # the semantic pass is correct + s.magic = b.magic + if b.typ != nil and b.typ.len > 0: + s.typ.n[0] = b.typ.n[0] + s.typ.flags = b.typ.flags + of bsNoDistinct: + localError(c.config, n.info, "borrow proc without distinct type parameter is meaningless") + of bsReturnNotMatch: + localError(c.config, n.info, "borrow from proc return type mismatch: '$1'" % typeToString(b.typ.returnType)) + of bsGeneric: + localError(c.config, n.info, "borrow with generic parameter is not supported") + of bsNotSupported: + localError(c.config, n.info, "borrow from '$1' is not supported" % $b.name.s) + else: + localError(c.config, n.info, errNoSymbolToBorrowFromFound) + +proc swapResult(n: PNode, sRes: PSym, dNode: PNode) = + ## Swap nodes that are (skResult) symbols to d(estination)Node. + for i in 0..<n.safeLen: + if n[i].kind == nkSym and n[i].sym == sRes: + n[i] = dNode + swapResult(n[i], sRes, dNode) + +proc addResult(c: PContext, n: PNode, t: PType, owner: TSymKind) = + template genResSym(s) = + var s = newSym(skResult, getIdent(c.cache, "result"), c.idgen, + getCurrOwner(c), n.info) + s.typ = t + incl(s.flags, sfUsed) + + if owner == skMacro or t != nil: + if n.len > resultPos and n[resultPos] != nil: + if n[resultPos].sym.kind != skResult: + localError(c.config, n.info, "incorrect result proc symbol") + if n[resultPos].sym.owner != getCurrOwner(c): + # re-write result with new ownership, and re-write the proc accordingly + let sResSym = n[resultPos].sym + genResSym(s) + n[resultPos] = newSymNode(s) + swapResult(n, sResSym, n[resultPos]) + c.p.resultSym = n[resultPos].sym + else: + genResSym(s) + c.p.resultSym = s + n.add newSymNode(c.p.resultSym) + addParamOrResult(c, c.p.resultSym, owner) + +proc semProcAnnotation(c: PContext, prc: PNode; + validPragmas: TSpecialWords): PNode = + # Mirrored with semVarMacroPragma + result = nil + var n = prc[pragmasPos] + if n == nil or n.kind == nkEmpty: return + for i in 0..<n.len: + let it = n[i] + let key = if it.kind in nkPragmaCallKinds and it.len >= 1: it[0] else: it + + trySuggestPragmas(c, key) + + if isPossibleMacroPragma(c, it, key): + # we transform ``proc p {.m, rest.}`` into ``m(do: proc p {.rest.})`` and + # let the semantic checker deal with it: + var x = newNodeI(nkCall, key.info) + x.add(key) + + if it.kind in nkPragmaCallKinds and it.len > 1: + # pass pragma arguments to the macro too: + for i in 1..<it.len: + x.add(it[i]) + + # Drop the pragma from the list, this prevents getting caught in endless + # recursion when the nkCall is semanticized + prc[pragmasPos] = copyExcept(n, i) + if prc[pragmasPos].kind != nkEmpty and prc[pragmasPos].len == 0: + prc[pragmasPos] = c.graph.emptyNode + + x.add(prc) + + # recursion assures that this works for multiple macro annotations too: + var r = semOverloadedCall(c, x, x, {skMacro, skTemplate}, {efNoUndeclared}) + if r == nil: + # Restore the old list of pragmas since we couldn't process this + prc[pragmasPos] = n + # No matching macro was found but there's always the possibility this may + # be a .pragma. template instead + continue + + doAssert r[0].kind == nkSym + let m = r[0].sym + case m.kind + of skMacro: result = semMacroExpr(c, r, r, m, {}) + of skTemplate: result = semTemplateExpr(c, r, m, {}) + else: + prc[pragmasPos] = n + continue + + doAssert result != nil + + return result + +proc semInferredLambda(c: PContext, pt: TypeMapping, n: PNode): PNode = + ## used for resolving 'auto' in lambdas based on their callsite + var n = n + let original = n[namePos].sym + let s = original #copySym(original, false) + #incl(s.flags, sfFromGeneric) + #s.owner = original + + n = replaceTypesInBody(c, pt, n, original) + result = n + s.ast = result + n[namePos].sym = s + n[genericParamsPos] = c.graph.emptyNode + # for LL we need to avoid wrong aliasing + let params = copyTree n.typ.n + s.typ = n.typ + for i in 1..<params.len: + if params[i].typ.kind in {tyTypeDesc, tyGenericParam, + tyFromExpr}+tyTypeClasses: + localError(c.config, params[i].info, "cannot infer type of parameter: " & + params[i].sym.name.s) + #params[i].sym.owner = s + openScope(c) + pushOwner(c, s) + addParams(c, params, skProc) + pushProcCon(c, s) + addResult(c, n, n.typ.returnType, skProc) + s.ast[bodyPos] = hloBody(c, semProcBody(c, n[bodyPos], n.typ.returnType)) + trackProc(c, s, s.ast[bodyPos]) + popProcCon(c) + popOwner(c) + closeScope(c) + if optOwnedRefs in c.config.globalOptions and result.typ != nil: + result.typ = makeVarType(c, result.typ, tyOwned) + # alternative variant (not quite working): + # var prc = arg[0].sym + # let inferred = c.semGenerateInstance(c, prc, m.bindings, arg.info) + # result = inferred.ast + # result.kind = arg.kind + +proc activate(c: PContext, n: PNode) = + # XXX: This proc is part of my plan for getting rid of + # forward declarations. stay tuned. + when false: + # well for now it breaks code ... + case n.kind + of nkLambdaKinds: + discard semLambda(c, n, {}) + of nkCallKinds: + for i in 1..<n.len: activate(c, n[i]) + else: + discard + +proc maybeAddResult(c: PContext, s: PSym, n: PNode) = + if s.kind == skMacro: + let resultType = sysTypeFromName(c.graph, n.info, "NimNode") + addResult(c, n, resultType, s.kind) + elif s.typ.returnType != nil and not isInlineIterator(s.typ): + addResult(c, n, s.typ.returnType, s.kind) + +proc canonType(c: PContext, t: PType): PType = + if t.kind == tySequence: + result = c.graph.sysTypes[tySequence] + else: + result = t + +proc prevDestructor(c: PContext; prevOp: PSym; obj: PType; info: TLineInfo) = + var msg = "cannot bind another '" & prevOp.name.s & "' to: " & typeToString(obj) + if sfOverridden notin prevOp.flags: + msg.add "; previous declaration was constructed here implicitly: " & (c.config $ prevOp.info) + else: + msg.add "; previous declaration was here: " & (c.config $ prevOp.info) + localError(c.config, info, errGenerated, msg) + +proc whereToBindTypeHook(c: PContext; t: PType): PType = + result = t + while true: + if result.kind in {tyGenericBody, tyGenericInst}: result = result.skipModifier + elif result.kind == tyGenericInvocation: result = result[0] + else: break + if result.kind in {tyObject, tyDistinct, tySequence, tyString}: + result = canonType(c, result) + +proc bindDupHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) = + let t = s.typ + var noError = false + let cond = t.len == 2 and t.returnType != nil + + if cond: + var obj = t.firstParamType + while true: + incl(obj.flags, tfHasAsgn) + if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.skipModifier + elif obj.kind == tyGenericInvocation: obj = obj.genericHead + else: break + + var res = t.returnType + while true: + if res.kind in {tyGenericBody, tyGenericInst}: res = res.skipModifier + elif res.kind == tyGenericInvocation: res = res.genericHead + else: break + + if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, res): + obj = canonType(c, obj) + let ao = getAttachedOp(c.graph, obj, op) + if ao == s: + discard "forward declared destructor" + elif ao.isNil and tfCheckedForDestructor notin obj.flags: + setAttachedOp(c.graph, c.module.position, obj, op, s) + else: + prevDestructor(c, ao, obj, n.info) + noError = true + if obj.owner.getModule != s.getModule: + localError(c.config, n.info, errGenerated, + "type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")") + + if not noError and sfSystemModule notin s.owner.flags: + localError(c.config, n.info, errGenerated, + "signature for '=dup' must be proc[T: object](x: T): T") + + incl(s.flags, sfUsed) + incl(s.flags, sfOverridden) + +proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp; suppressVarDestructorWarning = false) = + let t = s.typ + var noError = false + let cond = case op + of attachedWasMoved: + t.len == 2 and t.returnType == nil and t.firstParamType.kind == tyVar + of attachedTrace: + t.len == 3 and t.returnType == nil and t.firstParamType.kind == tyVar and t[2].kind == tyPointer + of attachedDestructor: + if c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}: + t.len == 2 and t.returnType == nil + else: + t.len == 2 and t.returnType == nil and t.firstParamType.kind == tyVar + else: + t.len >= 2 and t.returnType == nil + + if cond: + var obj = t.firstParamType.skipTypes({tyVar}) + while true: + incl(obj.flags, tfHasAsgn) + if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.skipModifier + elif obj.kind == tyGenericInvocation: obj = obj.genericHead + else: break + if obj.kind in {tyObject, tyDistinct, tySequence, tyString}: + if (not suppressVarDestructorWarning) and op == attachedDestructor and t.firstParamType.kind == tyVar and + c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}: + message(c.config, n.info, warnDeprecated, "A custom '=destroy' hook which takes a 'var T' parameter is deprecated; it should take a 'T' parameter") + obj = canonType(c, obj) + let ao = getAttachedOp(c.graph, obj, op) + if ao == s: + discard "forward declared destructor" + elif ao.isNil and tfCheckedForDestructor notin obj.flags: + setAttachedOp(c.graph, c.module.position, obj, op, s) + else: + prevDestructor(c, ao, obj, n.info) + noError = true + if obj.owner.getModule != s.getModule: + localError(c.config, n.info, errGenerated, + "type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")") + if not noError and sfSystemModule notin s.owner.flags: + case op + of attachedTrace: + localError(c.config, n.info, errGenerated, + "signature for '=trace' must be proc[T: object](x: var T; env: pointer)") + of attachedDestructor: + if c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}: + localError(c.config, n.info, errGenerated, + "signature for '=destroy' must be proc[T: object](x: var T) or proc[T: object](x: T)") + else: + localError(c.config, n.info, errGenerated, + "signature for '=destroy' must be proc[T: object](x: var T)") + else: + localError(c.config, n.info, errGenerated, + "signature for '" & s.name.s & "' must be proc[T: object](x: var T)") + incl(s.flags, sfUsed) + incl(s.flags, sfOverridden) + +proc semOverride(c: PContext, s: PSym, n: PNode) = + let name = s.name.s.normalize + case name + of "=destroy": + bindTypeHook(c, s, n, attachedDestructor) + if s.ast != nil: + if s.ast[pragmasPos].kind == nkEmpty: + s.ast[pragmasPos] = newNodeI(nkPragma, s.info) + s.ast[pragmasPos].add newTree(nkExprColonExpr, + newIdentNode(c.cache.getIdent("raises"), s.info), newNodeI(nkBracket, s.info)) + of "deepcopy", "=deepcopy": + if s.typ.len == 2 and + s.typ.firstParamType.skipTypes(abstractInst).kind in {tyRef, tyPtr} and + sameType(s.typ.firstParamType, s.typ.returnType): + # Note: we store the deepCopy in the base of the pointer to mitigate + # the problem that pointers are structural types: + var t = s.typ.firstParamType.skipTypes(abstractInst).elementType.skipTypes(abstractInst) + while true: + if t.kind == tyGenericBody: t = t.typeBodyImpl + elif t.kind == tyGenericInvocation: t = t.genericHead + else: break + if t.kind in {tyObject, tyDistinct, tyEnum, tySequence, tyString}: + if getAttachedOp(c.graph, t, attachedDeepCopy).isNil: + setAttachedOp(c.graph, c.module.position, t, attachedDeepCopy, s) + else: + localError(c.config, n.info, errGenerated, + "cannot bind another 'deepCopy' to: " & typeToString(t)) + else: + localError(c.config, n.info, errGenerated, + "cannot bind 'deepCopy' to: " & typeToString(t)) + + if t.owner.getModule != s.getModule: + localError(c.config, n.info, errGenerated, + "type bound operation `" & name & "` can be defined only in the same module with its type (" & t.typeToString() & ")") + + else: + localError(c.config, n.info, errGenerated, + "signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T") + incl(s.flags, sfUsed) + incl(s.flags, sfOverridden) + of "=", "=copy", "=sink": + if s.magic == mAsgn: return + incl(s.flags, sfUsed) + incl(s.flags, sfOverridden) + if name == "=": + message(c.config, n.info, warnDeprecated, "Overriding `=` hook is deprecated; Override `=copy` hook instead") + let t = s.typ + if t.len == 3 and t.returnType == nil and t.firstParamType.kind == tyVar: + var obj = t.firstParamType.elementType + while true: + incl(obj.flags, tfHasAsgn) + if obj.kind == tyGenericBody: obj = obj.skipModifier + elif obj.kind == tyGenericInvocation: obj = obj.genericHead + else: break + var objB = t[2] + while true: + if objB.kind == tyGenericBody: objB = objB.skipModifier + elif objB.kind in {tyGenericInvocation, tyGenericInst}: + objB = objB.genericHead + else: break + if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, objB): + # attach these ops to the canonical tySequence + obj = canonType(c, obj) + #echo "ATTACHING TO ", obj.id, " ", s.name.s, " ", cast[int](obj) + let k = if name == "=" or name == "=copy": attachedAsgn else: attachedSink + let ao = getAttachedOp(c.graph, obj, k) + if ao == s: + discard "forward declared op" + elif ao.isNil and tfCheckedForDestructor notin obj.flags: + setAttachedOp(c.graph, c.module.position, obj, k, s) + else: + prevDestructor(c, ao, obj, n.info) + if obj.owner.getModule != s.getModule: + localError(c.config, n.info, errGenerated, + "type bound operation `" & name & "` can be defined only in the same module with its type (" & obj.typeToString() & ")") + + return + if sfSystemModule notin s.owner.flags: + localError(c.config, n.info, errGenerated, + "signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)") + of "=trace": + if s.magic != mTrace: + bindTypeHook(c, s, n, attachedTrace) + of "=wasmoved": + if s.magic != mWasMoved: + bindTypeHook(c, s, n, attachedWasMoved) + of "=dup": + if s.magic != mDup: + bindDupHook(c, s, n, attachedDup) + else: + if sfOverridden in s.flags: + localError(c.config, n.info, errGenerated, + "'destroy' or 'deepCopy' expected for 'override'") + +proc cursorInProcAux(conf: ConfigRef; n: PNode): bool = + result = false + if inCheckpoint(n.info, conf.m.trackPos) != cpNone: return true + for i in 0..<n.safeLen: + if cursorInProcAux(conf, n[i]): return true + +proc cursorInProc(conf: ConfigRef; n: PNode): bool = + if n.info.fileIndex == conf.m.trackPos.fileIndex: + result = cursorInProcAux(conf, n) + else: + result = false + +proc hasObjParam(s: PSym): bool = + result = false + var t = s.typ + for col in 1..<t.len: + if skipTypes(t[col], skipPtrs).kind == tyObject: + return true + +proc finishMethod(c: PContext, s: PSym) = + if hasObjParam(s): + methodDef(c.graph, c.idgen, s) + +proc semCppMember(c: PContext; s: PSym; n: PNode) = + if sfImportc notin s.flags: + let isVirtual = sfVirtual in s.flags + let isCtor = sfConstructor in s.flags + let pragmaName = if isVirtual: "virtual" elif isCtor: "constructor" else: "member" + if c.config.backend == backendCpp: + if s.typ.len < 2 and not isCtor: + localError(c.config, n.info, pragmaName & " must have at least one parameter") + for son in s.typ.signature: + if son!=nil and son.isMetaType: + localError(c.config, n.info, pragmaName & " unsupported for generic routine") + var typ: PType + if isCtor: + typ = s.typ.returnType + if typ == nil or typ.kind != tyObject: + localError(c.config, n.info, "constructor must return an object") + if sfImportc in typ.sym.flags: + localError(c.config, n.info, "constructor in an imported type needs importcpp pragma") + else: + typ = s.typ.firstParamType + if typ.kind == tyPtr and not isCtor: + typ = typ.elementType + if typ.kind != tyObject: + localError(c.config, n.info, pragmaName & " must be either ptr to object or object type.") + if typ.owner.id == s.owner.id and c.module.id == s.owner.id: + c.graph.memberProcsPerType.mgetOrPut(typ.itemId, @[]).add s + else: + localError(c.config, n.info, + pragmaName & " procs must be defined in the same scope as the type they are virtual for and it must be a top level scope") + else: + localError(c.config, n.info, pragmaName & " procs are only supported in C++") + else: + var typ = s.typ.returnType + if typ != nil and typ.kind == tyObject and typ.itemId notin c.graph.initializersPerType: + var initializerCall = newTree(nkCall, newSymNode(s)) + var isInitializer = n[paramsPos].len > 1 + for i in 1..<n[paramsPos].len: + let p = n[paramsPos][i] + let val = p[^1] + if val.kind == nkEmpty: + isInitializer = false + break + var j = 0 + while p[j].sym.kind == skParam: + initializerCall.add val + inc j + if isInitializer: + c.graph.initializersPerType[typ.itemId] = initializerCall + +proc semMethodPrototype(c: PContext; s: PSym; n: PNode) = + if s.isGenericRoutine: + let tt = s.typ + var foundObj = false + # we start at 1 for now so that tparsecombnum continues to compile. + # XXX Revisit this problem later. + for col in 1..<tt.len: + let t = tt[col] + if t != nil and t.kind == tyGenericInvocation: + var x = skipTypes(t.genericHead, {tyVar, tyLent, tyPtr, tyRef, tyGenericInst, + tyGenericInvocation, tyGenericBody, + tyAlias, tySink, tyOwned}) + if x.kind == tyObject and t.len-1 == n[genericParamsPos].len: + foundObj = true + addMethodToGeneric(c.graph, c.module.position, x, col, s) + message(c.config, n.info, warnDeprecated, "generic methods are deprecated") + #if not foundObj: + # message(c.config, n.info, warnDeprecated, "generic method not attachable to object type is deprecated") + else: + # why check for the body? bug #2400 has none. Checking for sfForward makes + # no sense either. + # and result[bodyPos].kind != nkEmpty: + if hasObjParam(s): + methodDef(c.graph, c.idgen, s) + else: + localError(c.config, n.info, "'method' needs a parameter that has an object type") + +proc semProcAux(c: PContext, n: PNode, kind: TSymKind, + validPragmas: TSpecialWords, flags: TExprFlags = {}): PNode = + result = semProcAnnotation(c, n, validPragmas) + if result != nil: return result + result = n + checkMinSonsLen(n, bodyPos + 1, c.config) + + let + isAnon = n[namePos].kind == nkEmpty + isHighlight = c.config.ideCmd == ideHighlight + + var s: PSym + + case n[namePos].kind + of nkEmpty: + s = newSym(kind, c.cache.idAnon, c.idgen, c.getCurrOwner, n.info) + s.flags.incl sfUsed + n[namePos] = newSymNode(s) + of nkSym: + s = n[namePos].sym + s.owner = c.getCurrOwner + else: + # Highlighting needs to be done early so the position for + # name isn't changed (see taccent_highlight). We don't want to check if this is the + # defintion yet since we are missing some info (comments, side effects) + s = semIdentDef(c, n[namePos], kind, reportToNimsuggest=isHighlight) + n[namePos] = newSymNode(s) + when false: + # disable for now + if sfNoForward in c.module.flags and + sfSystemModule notin c.module.flags: + addInterfaceOverloadableSymAt(c, c.currentScope, s) + s.flags.incl sfForward + return + + assert s.kind in skProcKinds + + s.ast = n + s.options = c.config.options + #s.scope = c.currentScope + if s.kind in {skMacro, skTemplate}: + # push noalias flag at first to prevent unwanted recursive calls: + incl(s.flags, sfNoalias) + + # before compiling the proc params & body, set as current the scope + # where the proc was declared + let declarationScope = c.currentScope + pushOwner(c, s) + openScope(c) + + # process parameters: + # generic parameters, parameters, and also the implicit generic parameters + # within are analysed. This is often the entirety of their semantic analysis + # but later we will have to do a check for forward declarations, which can by + # way of pragmas, default params, and so on invalidate this parsing. + # Nonetheless, we need to carry out this analysis to perform the search for a + # potential forward declaration. + setGenericParamsMisc(c, n) + + if n[paramsPos].kind != nkEmpty: + semParamList(c, n[paramsPos], n[genericParamsPos], s) + else: + s.typ = newProcType(c, n.info) + + if n[genericParamsPos].safeLen == 0: + # if there exist no explicit or implicit generic parameters, then this is + # at most a nullary generic (generic with no type params). Regardless of + # whether it's a nullary generic or non-generic, we restore the original. + # In the case of `nkEmpty` it's non-generic and an empty `nkGeneircParams` + # is a nullary generic. + # + # Remarks about nullary generics vs non-generics: + # The difference between a non-generic and nullary generic is minor in + # most cases but there are subtle and significant differences as well. + # Due to instantiation that generic procs go through, a static echo in the + # body of a nullary generic will not be executed immediately, as it's + # instantiated and not immediately evaluated. + n[genericParamsPos] = n[miscPos][1] + n[miscPos] = c.graph.emptyNode + + if tfTriggersCompileTime in s.typ.flags: incl(s.flags, sfCompileTime) + if n[patternPos].kind != nkEmpty: + n[patternPos] = semPattern(c, n[patternPos], s) + if s.kind == skIterator: + s.typ.flags.incl(tfIterator) + elif s.kind == skFunc: + incl(s.flags, sfNoSideEffect) + incl(s.typ.flags, tfNoSideEffect) + + var (proto, comesFromShadowScope) = + if isAnon: (nil, false) + else: searchForProc(c, declarationScope, s) + if proto == nil and sfForward in s.flags and n[bodyPos].kind != nkEmpty: + ## In cases such as a macro generating a proc with a gensymmed name we + ## know `searchForProc` will not find it and sfForward will be set. In + ## such scenarios the sym is shared between forward declaration and we + ## can treat the `s` as the proto. + ## To differentiate between that happening and a macro just returning a + ## forward declaration that has been typed before we check if the body + ## is not empty. This has the sideeffect of allowing multiple forward + ## declarations if they share the same sym. + ## See the "doubly-typed forward decls" case in tmacros_issues.nim + proto = s + let hasProto = proto != nil + + # set the default calling conventions + case s.kind + of skIterator: + if s.typ.callConv != ccClosure: + s.typ.callConv = if isAnon: ccClosure else: ccInline + of skMacro, skTemplate: + # we don't bother setting calling conventions for macros and templates + discard + else: + # NB: procs with a forward decl have theirs determined by the forward decl + if not hasProto: + # in this case we're either a forward declaration or we're an impl without + # a forward decl. We set the calling convention or will be set during + # pragma analysis further down. + s.typ.callConv = lastOptionEntry(c).defaultCC + + if not hasProto and sfGenSym notin s.flags: #and not isAnon: + if s.kind in OverloadableSyms: + addInterfaceOverloadableSymAt(c, declarationScope, s) + else: + addInterfaceDeclAt(c, declarationScope, s) + + pragmaCallable(c, s, n, validPragmas) + if not hasProto: + implicitPragmas(c, s, n.info, validPragmas) + + if n[pragmasPos].kind != nkEmpty and sfBorrow notin s.flags: + setEffectsForProcType(c.graph, s.typ, n[pragmasPos], s) + s.typ.flags.incl tfEffectSystemWorkaround + + # To ease macro generation that produce forwarded .async procs we now + # allow a bit redundancy in the pragma declarations. The rule is + # a prototype's pragma list must be a superset of the current pragma + # list. + # XXX This needs more checks eventually, for example that external + # linking names do agree: + if hasProto and ( + # calling convention mismatch + tfExplicitCallConv in s.typ.flags and proto.typ.callConv != s.typ.callConv or + # implementation has additional pragmas + proto.typ.flags < s.typ.flags): + localError(c.config, n[pragmasPos].info, errPragmaOnlyInHeaderOfProcX % + ("'" & proto.name.s & "' from " & c.config$proto.info & + " '" & s.name.s & "' from " & c.config$s.info)) + + styleCheckDef(c, s) + if hasProto: + onDefResolveForward(n[namePos].info, proto) + else: + onDef(n[namePos].info, s) + + if hasProto: + if sfForward notin proto.flags and proto.magic == mNone: + wrongRedefinition(c, n.info, proto.name.s, proto.info) + if not comesFromShadowScope: + excl(proto.flags, sfForward) + incl(proto.flags, sfWasForwarded) + suggestSym(c.graph, s.info, proto, c.graph.usageSym) + closeScope(c) # close scope with wrong parameter symbols + openScope(c) # open scope for old (correct) parameter symbols + if proto.ast[genericParamsPos].isGenericParams: + addGenericParamListToScope(c, proto.ast[genericParamsPos]) + addParams(c, proto.typ.n, proto.kind) + proto.info = s.info # more accurate line information + proto.options = s.options + s = proto + n[genericParamsPos] = proto.ast[genericParamsPos] + n[paramsPos] = proto.ast[paramsPos] + n[pragmasPos] = proto.ast[pragmasPos] + if n[namePos].kind != nkSym: internalError(c.config, n.info, "semProcAux") + n[namePos].sym = proto + if importantComments(c.config) and proto.ast.comment.len > 0: + n.comment = proto.ast.comment + proto.ast = n # needed for code generation + popOwner(c) + pushOwner(c, s) + + if not isAnon: + if sfOverridden in s.flags or s.name.s[0] == '=': semOverride(c, s, n) + elif s.name.s[0] in {'.', '('}: + if s.name.s in [".", ".()", ".="] and {Feature.destructor, dotOperators} * c.features == {}: + localError(c.config, n.info, "the overloaded " & s.name.s & + " operator has to be enabled with {.experimental: \"dotOperators\".}") + elif s.name.s == "()" and callOperator notin c.features: + localError(c.config, n.info, "the overloaded " & s.name.s & + " operator has to be enabled with {.experimental: \"callOperator\".}") + + if sfBorrow in s.flags and c.config.cmd notin cmdDocLike: + result[bodyPos] = c.graph.emptyNode + + if sfCppMember * s.flags != {} and sfWasForwarded notin s.flags: + semCppMember(c, s, n) + + if n[bodyPos].kind != nkEmpty and sfError notin s.flags: + # for DLL generation we allow sfImportc to have a body, for use in VM + if c.config.ideCmd in {ideSug, ideCon} and s.kind notin {skMacro, skTemplate} and not + cursorInProc(c.config, n[bodyPos]): + # speed up nimsuggest + if s.kind == skMethod: semMethodPrototype(c, s, n) + elif isAnon: + let gp = n[genericParamsPos] + if gp.kind == nkEmpty or (gp.len == 1 and tfRetType in gp[0].typ.flags): + # absolutely no generics (empty) or a single generic return type are + # allowed, everything else, including a nullary generic is an error. + pushProcCon(c, s) + addResult(c, n, s.typ.returnType, skProc) + s.ast[bodyPos] = hloBody(c, semProcBody(c, n[bodyPos], s.typ.returnType)) + trackProc(c, s, s.ast[bodyPos]) + popProcCon(c) + elif efOperand notin flags: + localError(c.config, n.info, errGenericLambdaNotAllowed) + else: + pushProcCon(c, s) + if n[genericParamsPos].kind == nkEmpty or s.kind in {skMacro, skTemplate}: + # Macros and Templates can have generic parameters, but they are only + # used for overload resolution (there is no instantiation of the symbol) + if s.kind notin {skMacro, skTemplate} and s.magic == mNone: paramsTypeCheck(c, s.typ) + maybeAddResult(c, s, n) + let resultType = + if s.kind == skMacro: + sysTypeFromName(c.graph, n.info, "NimNode") + elif not isInlineIterator(s.typ): + s.typ.returnType + else: + nil + # semantic checking also needed with importc in case used in VM + s.ast[bodyPos] = hloBody(c, semProcBody(c, n[bodyPos], resultType)) + # unfortunately we cannot skip this step when in 'system.compiles' + # context as it may even be evaluated in 'system.compiles': + trackProc(c, s, s.ast[bodyPos]) + else: + if (s.typ.returnType != nil and s.kind != skIterator): + addDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), c.idgen, s, n.info)) + + openScope(c) + n[bodyPos] = semGenericStmt(c, n[bodyPos]) + closeScope(c) + if s.magic == mNone: + fixupInstantiatedSymbols(c, s) + if s.kind == skMethod: semMethodPrototype(c, s, n) + popProcCon(c) + else: + if s.kind == skMethod: semMethodPrototype(c, s, n) + if hasProto: localError(c.config, n.info, errImplOfXexpected % proto.name.s) + if {sfImportc, sfBorrow, sfError} * s.flags == {} and s.magic == mNone: + # this is a forward declaration and we're building the prototype + if s.kind in {skProc, skFunc} and s.typ.returnType != nil and s.typ.returnType.kind == tyAnything: + localError(c.config, n[paramsPos][0].info, "return type 'auto' cannot be used in forward declarations") + + incl(s.flags, sfForward) + incl(s.flags, sfWasForwarded) + elif sfBorrow in s.flags: semBorrow(c, n, s) + sideEffectsCheck(c, s) + + closeScope(c) # close scope for parameters + # c.currentScope = oldScope + popOwner(c) + if n[patternPos].kind != nkEmpty: + c.patterns.add(s) + if isAnon: + n.transitionSonsKind(nkLambda) + result.typ = s.typ + if optOwnedRefs in c.config.globalOptions: + result.typ = makeVarType(c, result.typ, tyOwned) + elif isTopLevel(c) and s.kind != skIterator and s.typ.callConv == ccClosure: + localError(c.config, s.info, "'.closure' calling convention for top level routines is invalid") + + # Prevent double highlights. We already highlighted before. + # When not highlighting we still need to allow for suggestions though + if not isHighlight: + suggestSym(c.graph, s.info, s, c.graph.usageSym) + +proc determineType(c: PContext, s: PSym) = + if s.typ != nil: return + #if s.magic != mNone: return + #if s.ast.isNil: return + discard semProcAux(c, s.ast, s.kind, {}) + +proc semIterator(c: PContext, n: PNode): PNode = + # gensym'ed iterator? + if n[namePos].kind == nkSym: + # gensym'ed iterators might need to become closure iterators: + n[namePos].sym.owner = getCurrOwner(c) + n[namePos].sym.transitionRoutineSymKind(skIterator) + result = semProcAux(c, n, skIterator, iteratorPragmas) + # bug #7093: if after a macro transformation we don't have an + # nkIteratorDef aynmore, return. The iterator then might have been + # sem'checked already. (Or not, if the macro skips it.) + if result.kind != n.kind: return + var s = result[namePos].sym + var t = s.typ + if t.returnType == nil and s.typ.callConv != ccClosure: + localError(c.config, n.info, "iterator needs a return type") + # iterators are either 'inline' or 'closure'; for backwards compatibility, + # we require first class iterators to be marked with 'closure' explicitly + # -- at least for 0.9.2. + if s.typ.callConv == ccClosure: + incl(s.typ.flags, tfCapturesEnv) + else: + s.typ.callConv = ccInline + if n[bodyPos].kind == nkEmpty and s.magic == mNone and c.inConceptDecl == 0: + localError(c.config, n.info, errImplOfXexpected % s.name.s) + if optOwnedRefs in c.config.globalOptions and result.typ != nil: + result.typ = makeVarType(c, result.typ, tyOwned) + result.typ.callConv = ccClosure + +proc semProc(c: PContext, n: PNode): PNode = + result = semProcAux(c, n, skProc, procPragmas) + +proc semFunc(c: PContext, n: PNode): PNode = + let validPragmas = if n[namePos].kind != nkEmpty: procPragmas + else: lambdaPragmas + result = semProcAux(c, n, skFunc, validPragmas) + +proc semMethod(c: PContext, n: PNode): PNode = + if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "method") + result = semProcAux(c, n, skMethod, methodPragmas) + # macros can transform converters to nothing: + if namePos >= result.safeLen: return result + # bug #7093: if after a macro transformation we don't have an + # nkIteratorDef aynmore, return. The iterator then might have been + # sem'checked already. (Or not, if the macro skips it.) + if result.kind != nkMethodDef: return + var s = result[namePos].sym + # we need to fix the 'auto' return type for the dispatcher here (see tautonotgeneric + # test case): + let disp = getDispatcher(s) + # auto return type? + if disp != nil and disp.typ.returnType != nil and disp.typ.returnType.kind == tyUntyped: + let ret = s.typ.returnType + disp.typ.setReturnType ret + if disp.ast[resultPos].kind == nkSym: + if isEmptyType(ret): disp.ast[resultPos] = c.graph.emptyNode + else: disp.ast[resultPos].sym.typ = ret + +proc semConverterDef(c: PContext, n: PNode): PNode = + if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "converter") + result = semProcAux(c, n, skConverter, converterPragmas) + # macros can transform converters to nothing: + if namePos >= result.safeLen: return result + # bug #7093: if after a macro transformation we don't have an + # nkIteratorDef aynmore, return. The iterator then might have been + # sem'checked already. (Or not, if the macro skips it.) + if result.kind != nkConverterDef: return + var s = result[namePos].sym + var t = s.typ + if t.returnType == nil: localError(c.config, n.info, errXNeedsReturnType % "converter") + if t.len != 2: localError(c.config, n.info, "a converter takes exactly one argument") + addConverterDef(c, LazySym(sym: s)) + +proc semMacroDef(c: PContext, n: PNode): PNode = + result = semProcAux(c, n, skMacro, macroPragmas) + # macros can transform macros to nothing: + if namePos >= result.safeLen: return result + # bug #7093: if after a macro transformation we don't have an + # nkIteratorDef aynmore, return. The iterator then might have been + # sem'checked already. (Or not, if the macro skips it.) + if result.kind != nkMacroDef: return + var s = result[namePos].sym + var t = s.typ + var allUntyped = true + var nullary = true + for i in 1..<t.n.len: + let param = t.n[i].sym + if param.typ.kind != tyUntyped: allUntyped = false + # no default value, parameters required in call + if param.ast == nil: nullary = false + if allUntyped: incl(s.flags, sfAllUntyped) + if nullary and n[genericParamsPos].kind == nkEmpty: + # macro can be called with alias syntax, remove pushed noalias flag + excl(s.flags, sfNoalias) + if n[bodyPos].kind == nkEmpty: + localError(c.config, n.info, errImplOfXexpected % s.name.s) + +proc incMod(c: PContext, n: PNode, it: PNode, includeStmtResult: PNode) = + var f = checkModuleName(c.config, it) + if f != InvalidFileIdx: + addIncludeFileDep(c, f) + onProcessing(c.graph, f, "include", c.module) + if containsOrIncl(c.includedFiles, f.int): + localError(c.config, n.info, errRecursiveDependencyX % toMsgFilename(c.config, f)) + else: + includeStmtResult.add semStmt(c, c.graph.includeFileCallback(c.graph, c.module, f), {}) + excl(c.includedFiles, f.int) + +proc evalInclude(c: PContext, n: PNode): PNode = + result = newNodeI(nkStmtList, n.info) + result.add n + template checkAs(it: PNode) = + if it.kind == nkInfix and it.len == 3: + let op = it[0].getPIdent + if op != nil and op.id == ord(wAs): + localError(c.config, it.info, "Cannot use '" & it[0].renderTree & "' in 'include'.") + for i in 0..<n.len: + let it = n[i] + checkAs(it) + if it.kind in {nkInfix, nkPrefix} and it[^1].kind == nkBracket: + let lastPos = it.len - 1 + var imp = copyNode(it) + newSons(imp, it.len) + for i in 0 ..< lastPos: imp[i] = it[i] + imp[lastPos] = imp[0] # dummy entry, replaced in the loop + for x in it[lastPos]: + checkAs(x) + imp[lastPos] = x + incMod(c, n, imp, result) + else: + incMod(c, n, it, result) + +proc recursiveSetFlag(n: PNode, flag: TNodeFlag) = + if n != nil: + for i in 0..<n.safeLen: recursiveSetFlag(n[i], flag) + incl(n.flags, flag) + +proc semPragmaBlock(c: PContext, n: PNode; expectedType: PType = nil): PNode = + checkSonsLen(n, 2, c.config) + let pragmaList = n[0] + pragma(c, nil, pragmaList, exprPragmas, isStatement = true) + + var inUncheckedAssignSection = 0 + for p in pragmaList: + if whichPragma(p) == wCast: + case whichPragma(p[1]) + of wGcSafe, wNoSideEffect, wTags, wForbids, wRaises: + discard "handled in sempass2" + of wUncheckedAssign: + inUncheckedAssignSection = 1 + else: + localError(c.config, p.info, "invalid pragma block: " & $p) + + inc c.inUncheckedAssignSection, inUncheckedAssignSection + n[1] = semExpr(c, n[1], expectedType = expectedType) + dec c.inUncheckedAssignSection, inUncheckedAssignSection + result = n + result.typ = n[1].typ + for i in 0..<pragmaList.len: + case whichPragma(pragmaList[i]) + of wLine: setInfoRecursive(result, pragmaList[i].info) + of wNoRewrite: recursiveSetFlag(result, nfNoRewrite) + else: discard + +proc semStaticStmt(c: PContext, n: PNode): PNode = + #echo "semStaticStmt" + #writeStackTrace() + inc c.inStaticContext + openScope(c) + let a = semStmt(c, n[0], {}) + closeScope(c) + dec c.inStaticContext + n[0] = a + evalStaticStmt(c.module, c.idgen, c.graph, a, c.p.owner) + when false: + # for incremental replays, keep the AST as required for replays: + result = n + else: + result = newNodeI(nkDiscardStmt, n.info, 1) + result[0] = c.graph.emptyNode + +proc usesResult(n: PNode): bool = + # nkStmtList(expr) properly propagates the void context, + # so we don't need to process that all over again: + if n.kind notin {nkStmtList, nkStmtListExpr, + nkMacroDef, nkTemplateDef} + procDefs: + if isAtom(n): + result = n.kind == nkSym and n.sym.kind == skResult + elif n.kind == nkReturnStmt: + result = true + else: + result = false + for c in n: + if usesResult(c): return true + else: + result = false + +proc inferConceptStaticParam(c: PContext, inferred, n: PNode) = + var typ = inferred.typ + let res = semConstExpr(c, n) + if not sameType(res.typ, typ.base): + localError(c.config, n.info, + "cannot infer the concept parameter '%s', due to a type mismatch. " & + "attempt to equate '%s' and '%s'." % [inferred.renderTree, $res.typ, $typ.base]) + typ.n = res + +proc semStmtList(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType = nil): PNode = + result = n + result.transitionSonsKind(nkStmtList) + var voidContext = false + var last = n.len-1 + # by not allowing for nkCommentStmt etc. we ensure nkStmtListExpr actually + # really *ends* in the expression that produces the type: The compiler now + # relies on this fact and it's too much effort to change that. And arguably + # 'R(); #comment' shouldn't produce R's type anyway. + #while last > 0 and n[last].kind in {nkPragma, nkCommentStmt, + # nkNilLit, nkEmpty}: + # dec last + for i in 0..<n.len: + var x = semExpr(c, n[i], flags, if i == n.len - 1: expectedType else: nil) + n[i] = x + if c.matchedConcept != nil and x.typ != nil and + (nfFromTemplate notin n.flags or i != last): + case x.typ.kind + of tyBool: + if x.kind == nkInfix and + x[0].kind == nkSym and + x[0].sym.name.s == "==": + if x[1].typ.isUnresolvedStatic: + inferConceptStaticParam(c, x[1], x[2]) + continue + elif x[2].typ.isUnresolvedStatic: + inferConceptStaticParam(c, x[2], x[1]) + continue + + let verdict = semConstExpr(c, n[i]) + if verdict == nil or verdict.kind != nkIntLit or verdict.intVal == 0: + localError(c.config, result.info, "concept predicate failed") + of tyFromExpr: continue + else: discard + if n[i].typ == c.enforceVoidContext: #or usesResult(n[i]): + voidContext = true + n.typ = c.enforceVoidContext + if i == last and (n.len == 1 or ({efWantValue, efInTypeof} * flags != {})): + n.typ = n[i].typ + if not isEmptyType(n.typ): n.transitionSonsKind(nkStmtListExpr) + elif i != last or voidContext: + discardCheck(c, n[i], flags) + else: + n.typ = n[i].typ + if not isEmptyType(n.typ): n.transitionSonsKind(nkStmtListExpr) + var m = n[i] + while m.kind in {nkStmtListExpr, nkStmtList} and m.len > 0: # from templates + m = m.lastSon + if endsInNoReturn(m): + for j in i + 1..<n.len: + case n[j].kind + of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkState: discard + else: message(c.config, n[j].info, warnUnreachableCode) + else: discard + + if result.len == 1 and + # concept bodies should be preserved as a stmt list: + c.matchedConcept == nil and + # also, don't make life complicated for macros. + # they will always expect a proper stmtlist: + nfBlockArg notin n.flags and + result[0].kind != nkDefer: + result = result[0] + +proc semStmt(c: PContext, n: PNode; flags: TExprFlags): PNode = + if efInTypeof notin flags: + result = semExprNoType(c, n) + else: + result = semExpr(c, n, flags) |