mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-01 04:59:05 +00:00
## Summary Adds `--genBif:on|off`, allowing regular compiler builds to generate per-module semantic BIF artifacts in `nimcache`. This reuses the semantic artifact format produced by incremental compilation without enabling IC or changing the normal code-generation and linking pipeline. In comparison to `nim check --compress ...` this new flag `nim c --genBif:on --compileOnly yourlib.nim` is considerably more useful for tooling. That produced full semantic proc declarations, Nim visibility, signatures, overload disambiguators, and pragmas. For a proc that was actually code-generated, it also recorded the exact backend name, for example. ## Motivation External tools such as language servers, debuggers, and binding generators can benefit from resolved symbol and type information produced during an ordinary build. Previously, these semantic BIF artifacts were tied to the incremental compiler workflow. ## Details With the option enabled: ```sh nim c --genBif:on project.nim ``` the compiler writes semantic `.s.bif` files and their supporting sidecars for each semantically checked module while continuing with the requested backend normally. The option: - Works with non-IC builds. - Does not enable incremental compilation. - Does not change generated program behavior. - Does not enable or introduce native ABI exports. - Does not generate `.abi.nif` manifests. - Is ignored for NimScript compilation. The `genBif` name follows existing artifact-generation options such as `genScript`, `genMapping`, and `genCDeps`. ## Testing Added a focused C backend test that runs a regular build with `--genBif:on` and verifies that semantic `.s.bif` artifacts are generated. A release-mode temporary compiler build and the focused Testament test both pass.
3087 lines
117 KiB
Nim
3087 lines
117 KiB
Nim
#
|
|
#
|
|
# 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.flagsImpl, 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
|
|
if noPanicOnExcept in c.graph.config.legacyFeatures:
|
|
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, 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, sfGlobal)
|
|
result.options = c.config.options
|
|
|
|
if reportToNimsuggest:
|
|
let info = getLineInfo(n)
|
|
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.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.ideActive: 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 checkDefineType(c: PContext; v: PSym; t: PType) =
|
|
# see semfold.foldDefine for acceptable types
|
|
let typeKinds =
|
|
case v.magic
|
|
of mStrDefine: {tyString, tyCstring}
|
|
# this used to be not typechecked, so anything that accepts int nodes for compatbility:
|
|
of mIntDefine: {tyInt..tyInt64, tyUInt..tyUInt64, tyBool, tyChar, tyEnum}
|
|
of mBoolDefine: {tyBool}
|
|
of mGenericDefine: {tyString, tyCstring, tyInt..tyInt64, tyUInt..tyUInt64, tyBool, tyEnum}
|
|
else: raiseAssert("unreachable")
|
|
var skipped = abstractVarRange
|
|
if v.magic == mGenericDefine:
|
|
# no distinct types for generic define
|
|
skipped.excl tyDistinct
|
|
if t.skipTypes(skipped).kind notin typeKinds:
|
|
let name =
|
|
case v.magic
|
|
of mStrDefine: "strdefine"
|
|
of mIntDefine: "intdefine"
|
|
of mBoolDefine: "booldefine"
|
|
of mGenericDefine: "define"
|
|
else: raiseAssert("unreachable")
|
|
localError(c.config, v.info, "unsupported type for constant '" & v.name.s &
|
|
"' with ." & name & " pragma: " & typeToString(t))
|
|
|
|
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))
|
|
if v.kind == skConst and v.magic in {mGenericDefine, mIntDefine, mStrDefine, mBoolDefine}:
|
|
checkDefineType(c, v, typ)
|
|
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 and sym.typ.callConv == ccClosure
|
|
|
|
proc usesLocalVar(n: PNode): bool =
|
|
case n.kind
|
|
of nkSym:
|
|
result = isLocalSym(n.sym)
|
|
of nkCallKinds, nkObjConstr:
|
|
result = false
|
|
for i in 1 ..< n.len:
|
|
if usesLocalVar(n[i]):
|
|
return true
|
|
of nkTupleConstr, nkPar, nkBracket, nkCurly:
|
|
result = false
|
|
for i in 0 ..< n.len:
|
|
if usesLocalVar(n[i]):
|
|
return true
|
|
of nkDotExpr, nkCheckedFieldExpr,
|
|
nkBracketExpr, nkAddr, nkHiddenAddr,
|
|
nkObjDownConv, nkObjUpConv:
|
|
result = usesLocalVar(n[0])
|
|
of nkHiddenStdConv, nkHiddenSubConv, nkCast, nkExprColonExpr:
|
|
result = usesLocalVar(n[1])
|
|
else:
|
|
result = false
|
|
|
|
proc globalVarInitCheck(c: PContext, n: PNode) =
|
|
if 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.flagsImpl.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.ideActive: 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: setOwner(v, c.p.owner)
|
|
when oKeepVariableNames:
|
|
if c.inUnrolledContext > 0: v.incl(sfShadowed)
|
|
else:
|
|
let shadowed = findShadowedVar(c, v)
|
|
if shadowed != nil:
|
|
shadowed.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.ideActive: 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: setOwner(v, 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
|
|
let iterType =
|
|
if iterBase.kind == tyIterable:
|
|
iterBase.skipModifier
|
|
else:
|
|
skipTypes(iterBase, {tyAlias, tySink, tyOwned})
|
|
var iter = skipTypes(iterType, {tyGenericInst})
|
|
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, 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.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: setOwner(v, getCurrOwner(c))
|
|
else:
|
|
var v = symForVar(c, n[0])
|
|
if getCurrOwner(c).kind == skModule: incl(v, sfGlobal)
|
|
# Use `iterType` here: it removes outer `tyIterable` / alias-like wrappers
|
|
# from the loop source, but still preserves `tyGenericInst` for the loop var.
|
|
v.typ = iterType
|
|
n[0] = newSymNode(v)
|
|
if sfGenSym notin v.flags and not isDiscardUnderscore(v): addDecl(c, v)
|
|
elif v.owner == nil: setOwner(v, 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, 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: setOwner(v, getCurrOwner(c))
|
|
else:
|
|
var v = symForVar(c, n[i])
|
|
if getCurrOwner(c).kind == skModule: incl(v, 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.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: setOwner(v, 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, flags: TExprFlags): 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, flags + {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
|
|
let iteratorFlags = flags * {efPreferIteratorForIterable}
|
|
n[^2] = semExprNoDeref(c, n[^2], iteratorFlags + {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], iteratorFlags)
|
|
elif n.len == 4:
|
|
n[^2] = implicitIterator(c, "pairs", n[^2], iteratorFlags)
|
|
else:
|
|
localError(c.config, n[^2].info, "iterator within for loop context expected")
|
|
result = semForVars(c, n, flags)
|
|
else:
|
|
result = semForVars(c, n, flags)
|
|
if n[^2].typ != nil and n[^2].typ.kind == tyIterable:
|
|
n[^2].typ = n[^2].typ.skipModifier
|
|
# 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.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)
|
|
if s.typ != nil:
|
|
# name node is a symbol with a type already, probably in resem, don't touch it
|
|
discard
|
|
else:
|
|
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: setOwner(s, 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.ideActive:
|
|
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.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.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
|
|
let preserveSym = s.typ != nil and s.typ.kind != tyForward and sfForward notin s.flags and
|
|
s.magic == mNone # magic might have received type above but still needs processing
|
|
if preserveSym:
|
|
# symbol already has a type, probably in resem, do not modify it
|
|
# but still semcheck the RHS to handle any defined symbols
|
|
# nominal type nodes are still ignored in semtypes
|
|
if a[1].kind != nkEmpty:
|
|
openScope(c)
|
|
pushOwner(c, s)
|
|
a[1] = semGenericParamList(c, a[1], nil)
|
|
inc c.inGenericContext
|
|
discard semTypeNode(c, a[2], s.typ)
|
|
dec c.inGenericContext
|
|
popOwner(c)
|
|
closeScope(c)
|
|
elif a[2].kind != nkEmpty:
|
|
pushOwner(c, s)
|
|
discard semTypeNode(c, a[2], s.typ)
|
|
popOwner(c)
|
|
elif 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, 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, 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, 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 and not preserveSym:
|
|
# 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, tfRefsAnonObj
|
|
let objTy = st.last
|
|
# add flags for `ref object` etc to underlying `object`
|
|
incl(objTy, 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, tfFinal)
|
|
let obj = newSym(skType, getIdent(c.cache, s.name.s & ":ObjectType"),
|
|
c.idgen, getCurrOwner(c), s.info)
|
|
obj.flagsImpl.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][0] = a[0][0][0] # ident "*"
|
|
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][0] = a[0][0] # ident "*"
|
|
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.incl sfPure
|
|
obj.typ = objTy
|
|
objTy.sym = obj
|
|
|
|
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) =
|
|
# each top level type needs to be processed, each epoch should reify at least one
|
|
var remainingOwners = initIntSet()
|
|
for (owner, _, _) in c.forwardTypeUpdates:
|
|
remainingOwners.incl owner.id
|
|
|
|
while c.forwardTypeUpdates.len > 0:
|
|
let pending = move c.forwardTypeUpdates
|
|
var madeProgress = false
|
|
|
|
for (owner, typ, typeNode) in pending:
|
|
# types that need to be updated due to containing forward types
|
|
# and their corresponding type nodes
|
|
# for example generic invocations of forward types end up here
|
|
var reified = semTypeNode(c, typeNode, nil)
|
|
assert reified != nil
|
|
assignType(typ, reified)
|
|
typ.itemId = reified.itemId # same id
|
|
if containsForwardType(typ):
|
|
c.forwardTypeUpdates.add (owner, typ, typeNode)
|
|
elif not remainingOwners.missingOrExcl(owner.id):
|
|
madeProgress = true
|
|
|
|
if not madeProgress:
|
|
# can't error here unfortunately
|
|
break
|
|
|
|
for (owner, field, expectedType) in c.forwardFieldUpdates:
|
|
semDelayedFieldDefault(c, owner, expectedType, field)
|
|
c.forwardFieldUpdates = @[]
|
|
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[0].kind == nkPragmaExpr:
|
|
let pragmas = a[0][1]
|
|
for i in 0 ..< pragmas.len:
|
|
if pragmas[i].kind == nkExprColonExpr and
|
|
pragmas[i][0].kind == nkIdent and
|
|
whichKeyword(pragmas[i][0].ident) == wSize:
|
|
if s.typ.kind != tyEnum and sfImportc notin s.flags:
|
|
# EventType* {.size: sizeof(uint32).} = enum
|
|
# AtomicFlag* {.importc: "atomic_flag", header: "<stdatomic.h>", size: 1.} = object
|
|
localError(c.config, pragmas[i].info, "size pragma only allowed for enum types and imported types")
|
|
|
|
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
|
|
var hasError = false
|
|
if x.kind in {nkObjectTy, nkTupleTy} or
|
|
(x.kind in {nkRefTy, nkPtrTy} and x.len == 1 and
|
|
x[0].kind in {nkObjectTy, nkTupleTy}):
|
|
# we need the 'safeSkipTypes' here because illegally recursive types
|
|
# can enter at this point, see bug #13763
|
|
let baseType = s.typ.safeSkipTypes(abstractPtrs)
|
|
if baseType.kind in {tyObject, tyTuple} and not baseType.n.isNil:
|
|
checkForMetaFields(c, baseType.n, hasError)
|
|
|
|
if s.typ.kind in {tySet, tyArray, tySequence, tyUncheckedArray} and s.typ.elementType.kind == tyNone:
|
|
# magic generics are not filled but tyNone is added to its elements by default,
|
|
# we lift them to tyBuiltInTypeClass here
|
|
s.typ = newTypeS(tyBuiltInTypeClass, c,
|
|
newTypeS(s.typ.kind, c))
|
|
|
|
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.flagsImpl, 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: LayeredIdTable, 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; op: TTypeAttachedOp; prevOp: PSym; obj: PType; info: TLineInfo) =
|
|
var msg = "cannot bind another '" & AttachedOpToStr[op] & "' to: " & typeToString(obj)
|
|
if prevOp == nil:
|
|
# happens if the destructor was implicitly constructed for a specific instance,
|
|
# not the entire generic type
|
|
msg.add "; previous declaration was constructed implicitly"
|
|
elif 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 checkedForDestructor(t: PType): bool =
|
|
if tfCheckedForDestructor in t.flags:
|
|
return true
|
|
# maybe another instance was instantiated, marking the generic root:
|
|
let root = genericRoot(t)
|
|
if root != nil and tfGenericHasDestructor in root.flags:
|
|
return true
|
|
result = false
|
|
|
|
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, 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 not checkedForDestructor(obj):
|
|
setAttachedOp(c.graph, c.module.position, obj, op, s)
|
|
else:
|
|
prevDestructor(c, op, 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.flagsImpl, sfUsed)
|
|
incl(s, sfOverridden)
|
|
|
|
proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
|
|
let t = s.typ
|
|
var noError = false
|
|
template notRefc: bool =
|
|
# fixes refc with non-var destructor; cancel warnings (#23156)
|
|
c.config.backend == backendJs or
|
|
c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}
|
|
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 notRefc:
|
|
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, 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}:
|
|
obj = canonType(c, obj)
|
|
let ao = getAttachedOp(c.graph, obj, op)
|
|
if ao == s:
|
|
discard "forward declared destructor"
|
|
elif ao.isNil and not checkedForDestructor(obj):
|
|
setAttachedOp(c.graph, c.module.position, obj, op, s)
|
|
else:
|
|
prevDestructor(c, op, 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 notRefc:
|
|
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.flagsImpl, sfUsed)
|
|
incl(s, 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.flagsImpl, sfUsed)
|
|
incl(s, sfOverridden)
|
|
of "=", "=copy", "=sink":
|
|
if s.magic == mAsgn: return
|
|
incl(s.flagsImpl, sfUsed)
|
|
incl(s, 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, 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 not checkedForDestructor(obj):
|
|
setAttachedOp(c.graph, c.module.position, obj, k, s)
|
|
else:
|
|
prevDestructor(c, k, 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 sameOwners(typ.owner, s.owner) and sameOwners(c.module, s.owner):
|
|
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].kind == nkSym and 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.flagsImpl.incl sfUsed
|
|
s.incl sfGenSym
|
|
n[namePos] = newSymNode(s)
|
|
of nkSym:
|
|
s = n[namePos].sym
|
|
setOwner(s, 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, 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, sfCompileTime)
|
|
if n[patternPos].kind != nkEmpty:
|
|
n[patternPos] = semPattern(c, n[patternPos], s)
|
|
if s.kind == skIterator:
|
|
s.typ.incl(tfIterator)
|
|
elif s.kind == skFunc:
|
|
incl(s, sfNoSideEffect)
|
|
incl(s.typ, 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 {sfError, sfExportc} * s.flags == {sfError, sfExportc}:
|
|
localError(c.config, n.info, "{.error.} and {.exportc.} pragmas are incompatible")
|
|
|
|
if n[pragmasPos].kind != nkEmpty and sfBorrow notin s.flags:
|
|
setEffectsForProcType(c.graph, s.typ, n[pragmasPos], s)
|
|
s.typ.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, sfForward)
|
|
incl(proto, 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` (the impl symbol) is discarded in favour of `proto`. It still carries
|
|
# `s.ast == n` (set above) and stays reachable as the owner of body-local
|
|
# symbols, so under IC it would be serialized as a SECOND, body-bearing
|
|
# `proc` entry — a phantom duplicate of `proto`. The per-module backend then
|
|
# codegens that phantom, whose `result` is owned by `proto` (addResult below
|
|
# re-parents it), not by the phantom: lambdalifting's capture check
|
|
# (`result.skipGenericOwner != owner`) then wrongly classifies `result` as a
|
|
# captured outer variable → "'result' … cannot be captured". Drop the
|
|
# discarded impl's body so it can never be emitted as a routine (same leak
|
|
# class the `miscPos` adoption below guards against for generic params).
|
|
let discardedImpl = s
|
|
s = proto
|
|
n[genericParamsPos] = proto.ast[genericParamsPos]
|
|
n[paramsPos] = proto.ast[paramsPos]
|
|
n[pragmasPos] = proto.ast[pragmasPos]
|
|
# miscPos holds this definition's *original* generic-param node (kept for
|
|
# error messages, see setGenericParamsMisc / issue #1713). For an impl that
|
|
# resolves to a forward decl, that node was analysed under the now-discarded
|
|
# impl symbol and its generic-param constraint types are owned by it. Adopt
|
|
# the prototype's miscPos so the discarded impl sym is fully unreachable —
|
|
# otherwise it leaks (via `proto.ast = n` below) as a type owner and gets
|
|
# serialized as a phantom duplicate overload under IC.
|
|
n[miscPos] = proto.ast[miscPos]
|
|
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
|
|
if discardedImpl != proto:
|
|
discardedImpl.ast = nil
|
|
# The impl symbol is discarded in favour of `proto`, but it stays `Complete`
|
|
# in this module, so `ast2nif.shouldWriteSymDef` still serializes it. With
|
|
# `sfExported` it would be written importable (`x` marker) and an importer
|
|
# would load BOTH it and `proto` into the overload set: "ambiguous call;
|
|
# both foo and foo" (identical signatures). Normally a discarded impl is a
|
|
# gensym/transient that isn't reached this way, but a `{.async: (raises).}`
|
|
# forward-decl + impl reconciles HERE with both syms exported. Strip the
|
|
# export so the design's "forward declarations are never importable" holds —
|
|
# the def still serializes (other refs may resolve to it) but is invisible
|
|
# to importer overload resolution; `proto` carries the export.
|
|
excl(discardedImpl, sfExported)
|
|
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\".}")
|
|
elif sfImportc notin s.flags and (s.name.s == ">" or s.name.s == ">=" or s.name.s == "!="):
|
|
# ignore imported procs as these operators in backend language might have different semantics
|
|
let op1 = if s.name.s == "!=": "==" elif s.name.s == ">": "<" else: "<="
|
|
message(c.config, n.info, warnInvalidCmpOp, "define `" & op1 & "` instead of `" & s.name.s & "` to implement user defined comparison operator. " &
|
|
"it allows you to use `" & s.name.s & "` automatically.")
|
|
|
|
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
|
|
|
|
let isInlineIterator = isInlineIterator(s.typ)
|
|
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':
|
|
|
|
if isInlineIterator and s.typ.callConv == ccClosure:
|
|
# iterators without explicit callconvs are lifted to closure,
|
|
# we need to add a result symbol for them
|
|
maybeAddResult(c, s, n)
|
|
|
|
|
|
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, sfForward)
|
|
incl(s, 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:
|
|
setOwner(n[namePos].sym, 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, tfCapturesEnv)
|
|
else:
|
|
s.typ.callConv = ccInline
|
|
if result[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, 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, sfAllUntyped)
|
|
if nullary and n[genericParamsPos].kind == nkEmpty:
|
|
# macro can be called with alias syntax, remove pushed noalias flag
|
|
excl(s, sfNoalias)
|
|
if n[bodyPos].kind == nkEmpty:
|
|
localError(c.config, n.info, errImplOfXexpected % s.name.s)
|
|
|
|
proc incMod(c: PContext, n: PNode, it: PNode, includeStmtResult, resolvedIncStmt: 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:
|
|
if resolvedIncStmt != nil:
|
|
resolvedIncStmt.add newStrNode(toFullPath(c.config, f), it.info)
|
|
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)
|
|
var resolvedIncStmt: PNode = nil
|
|
if {optCompress, optGenBif} * c.config.globalOptions != {} or
|
|
c.config.cmd == cmdM:
|
|
# New resolve the include filenames to string literals that contain absolute paths,
|
|
# nicer for IC:
|
|
resolvedIncStmt = newNodeI(nkIncludeStmt, n.info)
|
|
result.add resolvedIncStmt
|
|
else:
|
|
# Legacy: Keep `include` statement as is:
|
|
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, resolvedIncStmt)
|
|
else:
|
|
incMod(c, n, it, result, resolvedIncStmt)
|
|
|
|
proc recursiveSetFlag(n: PNode, flag: TNodeFlag) =
|
|
if n != nil:
|
|
for i in 0..<n.safeLen: recursiveSetFlag(n[i], flag)
|
|
incl(n.flags, flag)
|
|
|
|
proc enterPragmaBlock(c: PContext): POptionEntry =
|
|
result = POptionEntry(options: c.config.options,
|
|
notes: c.config.notes,
|
|
warningAsErrors: c.config.warningAsErrors,
|
|
features: c.features)
|
|
|
|
proc leavePragmaBlock(c: PContext, p: POptionEntry) =
|
|
c.config.options = p.options
|
|
c.config.notes = p.notes
|
|
c.config.warningAsErrors = p.warningAsErrors
|
|
c.features = p.features
|
|
|
|
proc semPragmaBlock(c: PContext, n: PNode; expectedType: PType = nil): PNode =
|
|
checkSonsLen(n, 2, c.config)
|
|
let pragmaList = n[0]
|
|
|
|
let oldOptionEntry = enterPragmaBlock(c)
|
|
|
|
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
|
|
|
|
leavePragmaBlock(c, oldOptionEntry)
|
|
|
|
proc semStaticStmt(c: PContext, n: PNode): PNode =
|
|
#echo "semStaticStmt"
|
|
#writeStackTrace()
|
|
let oldErrorCount = c.config.errorCounter
|
|
inc c.inStaticContext
|
|
openScope(c)
|
|
let a = semStmt(c, n[0], {})
|
|
closeScope(c)
|
|
dec c.inStaticContext
|
|
n[0] = a
|
|
if c.config.errorCounter == oldErrorCount:
|
|
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)
|