mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-29 09:24:36 +00:00
fixes #16376
The way the compiler handled generic proc instantiations in calls (like
`foo[int](...)`) up to this point was to instantiate `foo[int]`, create
a symbol for the instantiated proc (or a symchoice for multiple procs
excluding ones with mismatching generic param counts), then perform
overload resolution on this symbol/symchoice. The exception to this was
when the called symbol was already a symchoice node, in which case it
wasn't instantiated and overloading was called directly ([these
lines](b7b1313d21/compiler/semexprs.nim (L3366-L3371))).
This has several problems:
* Templates and macros can't create instantiated symbols, so they
couldn't participate in overloaded explicit generic instantiations,
causing the issue #16376.
* Every single proc that can be instantiated with the given generic
params is fully instantiated including the body. #9997 is about this but
isn't fixed here since the instantiation isn't in a call.
The way overload resolution handles explicit instantiations by itself is
also buggy:
* It doesn't check constraints.
* It allows only partially providing the generic parameters, which makes
sense for implicit generics, but can cause ambiguity in overloading.
Here is how this PR deals with these problems:
* Overload resolution now always handles explicit generic instantiations
in calls, in `initCandidate`, as long as the symbol resolves to a
routine symbol.
* Overload resolution now checks the generic params for constraints and
correct parameter count (ignoring implicit params). If these don't
match, the entire overload is considered as not matching and not
instantiated.
* Special error messages are added for mismatching/missing/extra generic
params. This is almost all of the diff in `semcall`.
* Procs with matching generic parameters now instantiate only the type
of the signature in overload resolution, not the proc itself, which also
works for templates and macros.
Unfortunately we can't entirely remove instantiations because overload
resolution can't handle some cases with uninstantiated types even though
it's resolved in the binding (see the last 2 blocks in
`texplicitgenerics`). There are also some instantiation issues with
default params that #24005 didn't fix but I didn't want this to become
the 3rd huge generics PR in a row so I didn't dive too deep into trying
to fix them. There is still a minor instantiation fix in `semtypinst`
though for subscripts in calls.
Additional changes:
* Overloading of `[]` wasn't documented properly, it somewhat is now
because we need to mention the limitation that it can't be done for
generic procs/types.
* Tests can now enable the new type mismatch errors with just
`-d:testsConciseTypeMismatch` in the command.
Package PRs:
- using fork for now:
[combparser](https://github.com/PMunch/combparser/pull/7) (partial
generic instantiation)
- merged: [cligen](https://github.com/c-blake/cligen/pull/233) (partial
generic instantiation but non-overloaded + template)
- merged: [neo](https://github.com/andreaferretti/neo/pull/56) (trying
to instantiate template with no generic param)
871 lines
33 KiB
Nim
871 lines
33 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 implements the semantic checking pass.
|
|
|
|
import
|
|
ast, options, astalgo, trees,
|
|
wordrecg, ropes, msgs, idents, renderer, types, platform,
|
|
magicsys, nversion, nimsets, semfold, modulepaths, importer,
|
|
procfind, lookups, pragmas, semdata, semtypinst, sigmatch,
|
|
transf, vmdef, vm, aliases, cgmeth, lambdalifting,
|
|
evaltempl, patterns, parampatterns, sempass2, linter, semmacrosanity,
|
|
lowerings, plugins/active, lineinfos, int128,
|
|
isolation_check, typeallowed, modulegraphs, enumtostr, concepts, astmsgs,
|
|
extccomp
|
|
|
|
import vtables
|
|
import std/[strtabs, math, tables, intsets, strutils, packedsets]
|
|
|
|
when not defined(leanCompiler):
|
|
import spawn
|
|
|
|
when defined(nimPreviewSlimSystem):
|
|
import std/[
|
|
formatfloat,
|
|
assertions,
|
|
]
|
|
|
|
# implementation
|
|
|
|
proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode
|
|
proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode
|
|
proc semExprNoType(c: PContext, n: PNode): PNode
|
|
proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode
|
|
proc semProcBody(c: PContext, n: PNode; expectedType: PType = nil): PNode
|
|
|
|
proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode
|
|
proc changeType(c: PContext; n: PNode, newType: PType, check: bool)
|
|
|
|
proc semTypeNode(c: PContext, n: PNode, prev: PType): PType
|
|
proc semStmt(c: PContext, n: PNode; flags: TExprFlags): PNode
|
|
proc semOpAux(c: PContext, n: PNode)
|
|
proc semParamList(c: PContext, n, genericParams: PNode, s: PSym)
|
|
proc addParams(c: PContext, n: PNode, kind: TSymKind)
|
|
proc maybeAddResult(c: PContext, s: PSym, n: PNode)
|
|
proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode
|
|
proc activate(c: PContext, n: PNode)
|
|
proc semQuoteAst(c: PContext, n: PNode): PNode
|
|
proc finishMethod(c: PContext, s: PSym)
|
|
proc evalAtCompileTime(c: PContext, n: PNode): PNode
|
|
proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode
|
|
proc semStaticExpr(c: PContext, n: PNode; expectedType: PType = nil): PNode
|
|
proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType
|
|
proc semTypeOf(c: PContext; n: PNode): PNode
|
|
proc computeRequiresInit(c: PContext, t: PType): bool
|
|
proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo)
|
|
proc hasUnresolvedArgs(c: PContext, n: PNode): bool
|
|
proc isArrayConstr(n: PNode): bool {.inline.} =
|
|
result = n.kind == nkBracket and
|
|
n.typ.skipTypes(abstractInst).kind == tyArray
|
|
|
|
template semIdeForTemplateOrGenericCheck(conf, n, requiresCheck) =
|
|
# we check quickly if the node is where the cursor is
|
|
when defined(nimsuggest):
|
|
if n.info.fileIndex == conf.m.trackPos.fileIndex and n.info.line == conf.m.trackPos.line:
|
|
requiresCheck = true
|
|
|
|
template semIdeForTemplateOrGeneric(c: PContext; n: PNode;
|
|
requiresCheck: bool) =
|
|
# use only for idetools support; this is pretty slow so generics and
|
|
# templates perform some quick check whether the cursor is actually in
|
|
# the generic or template.
|
|
when defined(nimsuggest):
|
|
if c.config.cmd == cmdIdeTools and requiresCheck:
|
|
#if optIdeDebug in gGlobalOptions:
|
|
# echo "passing to safeSemExpr: ", renderTree(n)
|
|
discard safeSemExpr(c, n)
|
|
|
|
proc fitNodePostMatch(c: PContext, formal: PType, arg: PNode): PNode =
|
|
let x = arg.skipConv
|
|
if (x.kind == nkCurly and formal.kind == tySet and formal.base.kind != tyGenericParam) or
|
|
(x.kind in {nkPar, nkTupleConstr}) and formal.kind notin {tyUntyped, tyBuiltInTypeClass, tyAnything}:
|
|
changeType(c, x, formal, check=true)
|
|
result = arg
|
|
result = skipHiddenSubConv(result, c.graph, c.idgen)
|
|
|
|
|
|
proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode =
|
|
if arg.typ.isNil:
|
|
localError(c.config, arg.info, "expression has no type: " &
|
|
renderTree(arg, {renderNoComments}))
|
|
# error correction:
|
|
result = copyTree(arg)
|
|
result.typ = formal
|
|
elif arg.kind in nkSymChoices and formal.skipTypes(abstractInst).kind == tyEnum:
|
|
# Pick the right 'sym' from the sym choice by looking at 'formal' type:
|
|
result = nil
|
|
for ch in arg:
|
|
if sameType(ch.typ, formal):
|
|
return ch
|
|
typeMismatch(c.config, info, formal, arg.typ, arg)
|
|
else:
|
|
result = indexTypesMatch(c, formal, arg.typ, arg)
|
|
if result == nil:
|
|
typeMismatch(c.config, info, formal, arg.typ, arg)
|
|
# error correction:
|
|
result = copyTree(arg)
|
|
result.typ = formal
|
|
else:
|
|
result = fitNodePostMatch(c, formal, result)
|
|
|
|
proc fitNodeConsiderViewType(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode =
|
|
let a = fitNode(c, formal, arg, info)
|
|
if formal.kind in {tyVar, tyLent}:
|
|
#classifyViewType(formal) != noView:
|
|
result = newNodeIT(nkHiddenAddr, a.info, formal)
|
|
result.add a
|
|
formal.flags.incl tfVarIsPtr
|
|
else:
|
|
result = a
|
|
|
|
proc inferWithMetatype(c: PContext, formal: PType,
|
|
arg: PNode, coerceDistincts = false): PNode
|
|
|
|
template commonTypeBegin*(): PType = PType(kind: tyUntyped)
|
|
|
|
proc commonType*(c: PContext; x, y: PType): PType =
|
|
# new type relation that is used for array constructors,
|
|
# if expressions, etc.:
|
|
if x == nil: return x
|
|
if y == nil: return y
|
|
var a = skipTypes(x, {tyGenericInst, tyAlias, tySink})
|
|
var b = skipTypes(y, {tyGenericInst, tyAlias, tySink})
|
|
result = x
|
|
if a.kind in {tyUntyped, tyNil}: result = y
|
|
elif b.kind in {tyUntyped, tyNil}: result = x
|
|
elif a.kind == tyTyped: result = a
|
|
elif b.kind == tyTyped: result = b
|
|
elif a.kind == tyTypeDesc:
|
|
# turn any concrete typedesc into the abstract typedesc type
|
|
if not a.hasElementType: result = a
|
|
else:
|
|
result = newType(tyTypeDesc, c.idgen, a.owner)
|
|
rawAddSon(result, newType(tyNone, c.idgen, a.owner))
|
|
elif b.kind in {tyArray, tySet, tySequence} and
|
|
a.kind == b.kind:
|
|
# check for seq[empty] vs. seq[int]
|
|
let idx = ord(b.kind == tyArray)
|
|
if a[idx].kind == tyEmpty: return y
|
|
elif a.kind == tyTuple and b.kind == tyTuple and sameTupleLengths(a, b):
|
|
var nt: PType = nil
|
|
for i, aa, bb in tupleTypePairs(a, b):
|
|
let aEmpty = isEmptyContainer(aa)
|
|
let bEmpty = isEmptyContainer(bb)
|
|
if aEmpty != bEmpty:
|
|
if nt.isNil:
|
|
nt = copyType(a, c.idgen, a.owner)
|
|
copyTypeProps(c.graph, c.idgen.module, nt, a)
|
|
|
|
nt[i] = if aEmpty: bb else: aa
|
|
if not nt.isNil: result = nt
|
|
#elif b[idx].kind == tyEmpty: return x
|
|
elif a.kind == tyRange and b.kind == tyRange:
|
|
# consider: (range[0..3], range[0..4]) here. We should make that
|
|
# range[0..4]. But then why is (range[0..4], 6) not range[0..6]?
|
|
# But then why is (2,4) not range[2..4]? But I think this would break
|
|
# too much code. So ... it's the same range or the base type. This means
|
|
# typeof(if b: 0 else 1) == int and not range[0..1]. For now. In the long
|
|
# run people expect ranges to work properly within a tuple.
|
|
if not sameType(a, b):
|
|
result = skipTypes(a, {tyRange}).skipIntLit(c.idgen)
|
|
when false:
|
|
if a.kind != tyRange and b.kind == tyRange:
|
|
# XXX This really needs a better solution, but a proper fix now breaks
|
|
# code.
|
|
result = a #.skipIntLit
|
|
elif a.kind == tyRange and b.kind != tyRange:
|
|
result = b #.skipIntLit
|
|
elif a.kind in IntegralTypes and a.n != nil:
|
|
result = a #.skipIntLit
|
|
elif a.kind == tyProc and b.kind == tyProc:
|
|
if a.callConv == ccClosure and b.callConv != ccClosure:
|
|
result = x
|
|
elif compatibleEffects(a, b) != efCompat or
|
|
(b.flags * {tfNoSideEffect, tfGcSafe}) < (a.flags * {tfNoSideEffect, tfGcSafe}):
|
|
result = y
|
|
else:
|
|
var k = tyNone
|
|
if a.kind in {tyRef, tyPtr}:
|
|
k = a.kind
|
|
if b.kind != a.kind: return x
|
|
# bug #7601, array construction of ptr generic
|
|
a = a.elementType.skipTypes({tyGenericInst})
|
|
b = b.elementType.skipTypes({tyGenericInst})
|
|
if a.kind == tyObject and b.kind == tyObject:
|
|
result = commonSuperclass(a, b)
|
|
# this will trigger an error later:
|
|
if result.isNil or result == a: return x
|
|
if result == b: return y
|
|
# bug #7906, tyRef/tyPtr + tyGenericInst of ref/ptr object ->
|
|
# ill-formed AST, no need for additional tyRef/tyPtr
|
|
if k != tyNone and x.kind != tyGenericInst:
|
|
let r = result
|
|
result = newType(k, c.idgen, r.owner)
|
|
result.addSonSkipIntLit(r, c.idgen)
|
|
|
|
const shouldChckCovered = {tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt64, tyBool}
|
|
proc shouldCheckCaseCovered(caseTyp: PType): bool =
|
|
result = false
|
|
case caseTyp.kind
|
|
of shouldChckCovered:
|
|
result = true
|
|
of tyRange:
|
|
if skipTypes(caseTyp[0], abstractInst).kind in shouldChckCovered:
|
|
result = true
|
|
else:
|
|
discard
|
|
|
|
proc endsInNoReturn(n: PNode): bool
|
|
|
|
proc commonType*(c: PContext; x: PType, y: PNode): PType =
|
|
# ignore exception raising branches in case/if expressions
|
|
if endsInNoReturn(y): return x
|
|
commonType(c, x, y.typ)
|
|
|
|
proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym =
|
|
result = newSym(kind, considerQuotedIdent(c, n), c.idgen, getCurrOwner(c), n.info)
|
|
when defined(nimsuggest):
|
|
suggestDecl(c, n, result)
|
|
|
|
proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym =
|
|
# like newSymS, but considers gensym'ed symbols
|
|
if n.kind == nkSym:
|
|
# and sfGenSym in n.sym.flags:
|
|
result = n.sym
|
|
if result.kind notin {kind, skTemp}:
|
|
localError(c.config, n.info, "cannot use symbol of kind '$1' as a '$2'" %
|
|
[result.kind.toHumanStr, kind.toHumanStr])
|
|
when false:
|
|
if sfGenSym in result.flags and result.kind notin {skTemplate, skMacro, skParam}:
|
|
# declarative context, so produce a fresh gensym:
|
|
result = copySym(result)
|
|
result.ast = n.sym.ast
|
|
put(c.p, n.sym, result)
|
|
# when there is a nested proc inside a template, semtmpl
|
|
# will assign a wrong owner during the first pass over the
|
|
# template; we must fix it here: see #909
|
|
result.owner = getCurrOwner(c)
|
|
else:
|
|
result = newSym(kind, considerQuotedIdent(c, n), c.idgen, getCurrOwner(c), n.info)
|
|
if find(result.name.s, '`') >= 0:
|
|
result.flags.incl sfWasGenSym
|
|
#if kind in {skForVar, skLet, skVar} and result.owner.kind == skModule:
|
|
# incl(result.flags, sfGlobal)
|
|
when defined(nimsuggest):
|
|
suggestDecl(c, n, result)
|
|
|
|
proc semIdentVis(c: PContext, kind: TSymKind, n: PNode,
|
|
allowed: TSymFlags): PSym
|
|
# identifier with visibility
|
|
proc semIdentWithPragma(c: PContext, kind: TSymKind, n: PNode,
|
|
allowed: TSymFlags, fromTopLevel = false): PSym
|
|
|
|
proc typeAllowedCheck(c: PContext; info: TLineInfo; typ: PType; kind: TSymKind;
|
|
flags: TTypeAllowedFlags = {}) =
|
|
let t = typeAllowed(typ, kind, c, flags)
|
|
if t != nil:
|
|
var err: string
|
|
if t == typ:
|
|
err = "invalid type: '$1' for $2" % [typeToString(typ), toHumanStr(kind)]
|
|
if kind in {skVar, skLet, skConst} and taIsTemplateOrMacro in flags:
|
|
err &= ". Did you mean to call the $1 with '()'?" % [toHumanStr(typ.owner.kind)]
|
|
else:
|
|
err = "invalid type: '$1' in this context: '$2' for $3" % [typeToString(t),
|
|
typeToString(typ), toHumanStr(kind)]
|
|
localError(c.config, info, err)
|
|
|
|
proc paramsTypeCheck(c: PContext, typ: PType) {.inline.} =
|
|
typeAllowedCheck(c, typ.n.info, typ, skProc)
|
|
|
|
proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym
|
|
proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode
|
|
proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode
|
|
proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
|
|
flags: TExprFlags = {}; expectedType: PType = nil): PNode
|
|
proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
|
|
flags: TExprFlags = {}; expectedType: PType = nil): PNode
|
|
|
|
when false:
|
|
proc createEvalContext(c: PContext, mode: TEvalMode): PEvalContext =
|
|
result = newEvalContext(c.module, mode)
|
|
result.getType = proc (n: PNode): PNode =
|
|
result = tryExpr(c, n)
|
|
if result == nil:
|
|
result = newSymNode(errorSym(c, n))
|
|
elif result.typ == nil:
|
|
result = newSymNode(getSysSym"void")
|
|
else:
|
|
result.typ = makeTypeDesc(c, result.typ)
|
|
|
|
result.handleIsOperator = proc (n: PNode): PNode =
|
|
result = isOpImpl(c, n)
|
|
|
|
proc hasCycle(n: PNode): bool =
|
|
result = false
|
|
incl n.flags, nfNone
|
|
for i in 0..<n.safeLen:
|
|
if nfNone in n[i].flags or hasCycle(n[i]):
|
|
result = true
|
|
break
|
|
excl n.flags, nfNone
|
|
|
|
proc fixupTypeAfterEval(c: PContext, evaluated, eOrig: PNode): PNode =
|
|
# recompute the types as 'eval' isn't guaranteed to construct types nor
|
|
# that the types are sound:
|
|
when true:
|
|
if eOrig.typ.kind in {tyUntyped, tyTyped, tyTypeDesc}:
|
|
result = semExprWithType(c, evaluated)
|
|
else:
|
|
result = evaluated
|
|
let expectedType = eOrig.typ.skipTypes({tyStatic})
|
|
if hasCycle(result):
|
|
result = localErrorNode(c, eOrig, "the resulting AST is cyclic and cannot be processed further")
|
|
else:
|
|
semmacrosanity.annotateType(result, expectedType, c.config)
|
|
else:
|
|
result = semExprWithType(c, evaluated)
|
|
#result = fitNode(c, e.typ, result) inlined with special case:
|
|
let arg = result
|
|
result = indexTypesMatch(c, eOrig.typ, arg.typ, arg)
|
|
if result == nil:
|
|
result = arg
|
|
# for 'tcnstseq' we support [] to become 'seq'
|
|
if eOrig.typ.skipTypes(abstractInst).kind == tySequence and
|
|
isArrayConstr(arg):
|
|
arg.typ = eOrig.typ
|
|
|
|
proc tryConstExpr(c: PContext, n: PNode; expectedType: PType = nil): PNode =
|
|
var e = semExprWithType(c, n, expectedType = expectedType)
|
|
if e == nil: return
|
|
|
|
result = getConstExpr(c.module, e, c.idgen, c.graph)
|
|
if result != nil: return
|
|
|
|
let oldErrorCount = c.config.errorCounter
|
|
let oldErrorMax = c.config.errorMax
|
|
let oldErrorOutputs = c.config.m.errorOutputs
|
|
|
|
c.config.m.errorOutputs = {}
|
|
c.config.errorMax = high(int) # `setErrorMaxHighMaybe` not appropriate here
|
|
|
|
when defined(nimsuggest):
|
|
# Remove the error hook so nimsuggest doesn't report errors there
|
|
let tempHook = c.graph.config.structuredErrorHook
|
|
c.graph.config.structuredErrorHook = nil
|
|
|
|
try:
|
|
result = evalConstExpr(c.module, c.idgen, c.graph, e)
|
|
if result == nil or result.kind == nkEmpty:
|
|
result = nil
|
|
else:
|
|
result = fixupTypeAfterEval(c, result, e)
|
|
|
|
except ERecoverableError:
|
|
result = nil
|
|
|
|
when defined(nimsuggest):
|
|
# Restore the error hook
|
|
c.graph.config.structuredErrorHook = tempHook
|
|
|
|
c.config.errorCounter = oldErrorCount
|
|
c.config.errorMax = oldErrorMax
|
|
c.config.m.errorOutputs = oldErrorOutputs
|
|
|
|
const
|
|
errConstExprExpected = "constant expression expected"
|
|
|
|
proc semConstExpr(c: PContext, n: PNode; expectedType: PType = nil): PNode =
|
|
var e = semExprWithType(c, n, expectedType = expectedType)
|
|
if e == nil:
|
|
localError(c.config, n.info, errConstExprExpected)
|
|
return n
|
|
if e.kind in nkSymChoices and e[0].typ.skipTypes(abstractInst).kind == tyEnum:
|
|
return e
|
|
result = getConstExpr(c.module, e, c.idgen, c.graph)
|
|
if result == nil:
|
|
#if e.kind == nkEmpty: globalError(n.info, errConstExprExpected)
|
|
result = evalConstExpr(c.module, c.idgen, c.graph, e)
|
|
if result == nil or result.kind == nkEmpty:
|
|
if e.info != n.info:
|
|
pushInfoContext(c.config, n.info)
|
|
localError(c.config, e.info, errConstExprExpected)
|
|
popInfoContext(c.config)
|
|
else:
|
|
localError(c.config, e.info, errConstExprExpected)
|
|
# error correction:
|
|
result = e
|
|
else:
|
|
result = fixupTypeAfterEval(c, result, e)
|
|
|
|
proc semExprFlagDispatched(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode =
|
|
if efNeedStatic in flags:
|
|
if efPreferNilResult in flags:
|
|
return tryConstExpr(c, n, expectedType)
|
|
else:
|
|
return semConstExpr(c, n, expectedType)
|
|
else:
|
|
result = semExprWithType(c, n, flags, expectedType)
|
|
if efPreferStatic in flags:
|
|
var evaluated = getConstExpr(c.module, result, c.idgen, c.graph)
|
|
if evaluated != nil: return evaluated
|
|
evaluated = evalAtCompileTime(c, result)
|
|
if evaluated != nil: return evaluated
|
|
|
|
proc semGenericStmt(c: PContext, n: PNode): PNode
|
|
|
|
include hlo, seminst, semcall
|
|
|
|
proc resetSemFlag(n: PNode) =
|
|
if n != nil:
|
|
excl n.flags, nfSem
|
|
for i in 0..<n.safeLen:
|
|
resetSemFlag(n[i])
|
|
|
|
proc semAfterMacroCall(c: PContext, call, macroResult: PNode,
|
|
s: PSym, flags: TExprFlags; expectedType: PType = nil): PNode =
|
|
## Semantically check the output of a macro.
|
|
## This involves processes such as re-checking the macro output for type
|
|
## coherence, making sure that variables declared with 'let' aren't
|
|
## reassigned, and binding the unbound identifiers that the macro output
|
|
## contains.
|
|
inc(c.config.evalTemplateCounter)
|
|
if c.config.evalTemplateCounter > evalTemplateLimit:
|
|
globalError(c.config, s.info, "template instantiation too nested")
|
|
c.friendModules.add(s.owner.getModule)
|
|
result = macroResult
|
|
resetSemFlag result
|
|
if s.typ.returnType == nil:
|
|
result = semStmt(c, result, flags)
|
|
else:
|
|
var retType = s.typ.returnType
|
|
if retType.kind == tyTypeDesc and tfUnresolved in retType.flags and
|
|
retType.hasElementType:
|
|
# bug #11941: template fails(T: type X, v: auto): T
|
|
# does not mean we expect a tyTypeDesc.
|
|
retType = retType.skipModifier
|
|
case retType.kind
|
|
of tyUntyped, tyAnything:
|
|
# Not expecting a type here allows templates like in ``tmodulealias.in``.
|
|
result = semExpr(c, result, flags, expectedType)
|
|
of tyTyped:
|
|
# More restrictive version.
|
|
result = semExprWithType(c, result, flags, expectedType)
|
|
of tyTypeDesc:
|
|
if result.kind == nkStmtList: result.transitionSonsKind(nkStmtListType)
|
|
var typ = semTypeNode(c, result, nil)
|
|
if typ == nil:
|
|
localError(c.config, result.info, "expression has no type: " &
|
|
renderTree(result, {renderNoComments}))
|
|
result = newSymNode(errorSym(c, result))
|
|
else:
|
|
result.typ = makeTypeDesc(c, typ)
|
|
#result = symNodeFromType(c, typ, n.info)
|
|
else:
|
|
if s.ast[genericParamsPos] != nil and retType.isMetaType:
|
|
# The return type may depend on the Macro arguments
|
|
# e.g. template foo(T: typedesc): seq[T]
|
|
# We will instantiate the return type here, because
|
|
# we now know the supplied arguments
|
|
var paramTypes = initTypeMapping()
|
|
for param, value in genericParamsInMacroCall(s, call):
|
|
var givenType = value.typ
|
|
# the sym nodes used for the supplied generic arguments for
|
|
# templates and macros leave type nil so regular sem can handle it
|
|
# in this case, get the type directly from the sym
|
|
if givenType == nil and value.kind == nkSym and value.sym.typ != nil:
|
|
givenType = value.sym.typ
|
|
idTablePut(paramTypes, param.typ, givenType)
|
|
|
|
retType = generateTypeInstance(c, paramTypes,
|
|
macroResult.info, retType)
|
|
|
|
if retType.kind == tyVoid:
|
|
result = semStmt(c, result, flags)
|
|
else:
|
|
result = semExpr(c, result, flags, expectedType)
|
|
result = fitNode(c, retType, result, result.info)
|
|
#globalError(s.info, errInvalidParamKindX, typeToString(s.typ.returnType))
|
|
dec(c.config.evalTemplateCounter)
|
|
discard c.friendModules.pop()
|
|
|
|
const
|
|
errMissingGenericParamsForTemplate = "'$1' has unspecified generic parameters"
|
|
errFloatToString = "cannot convert '$1' to '$2'"
|
|
|
|
proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
|
|
flags: TExprFlags = {}; expectedType: PType = nil): PNode =
|
|
rememberExpansion(c, nOrig.info, sym)
|
|
pushInfoContext(c.config, nOrig.info, sym.detailedInfo)
|
|
|
|
let info = getCallLineInfo(n)
|
|
markUsed(c, info, sym)
|
|
onUse(info, sym)
|
|
if sym == c.p.owner:
|
|
globalError(c.config, info, "recursive dependency: '$1'" % sym.name.s)
|
|
|
|
let genericParams = sym.ast[genericParamsPos].len
|
|
let suppliedParams = max(n.safeLen - 1, 0)
|
|
|
|
if suppliedParams < genericParams:
|
|
globalError(c.config, info, errMissingGenericParamsForTemplate % n.renderTree)
|
|
|
|
#if c.evalContext == nil:
|
|
# c.evalContext = c.createEvalContext(emStatic)
|
|
result = evalMacroCall(c.module, c.idgen, c.graph, c.templInstCounter, n, nOrig, sym)
|
|
if efNoSemCheck notin flags:
|
|
result = semAfterMacroCall(c, n, result, sym, flags, expectedType)
|
|
if c.config.macrosToExpand.hasKey(sym.name.s):
|
|
message(c.config, nOrig.info, hintExpandMacro, renderTree(result))
|
|
result = wrapInComesFrom(nOrig.info, sym, result)
|
|
popInfoContext(c.config)
|
|
|
|
proc forceBool(c: PContext, n: PNode): PNode =
|
|
result = fitNode(c, getSysType(c.graph, n.info, tyBool), n, n.info)
|
|
if result == nil: result = n
|
|
|
|
proc semConstBoolExpr(c: PContext, n: PNode): PNode =
|
|
result = forceBool(c, semConstExpr(c, n, getSysType(c.graph, n.info, tyBool)))
|
|
if result.kind != nkIntLit:
|
|
localError(c.config, n.info, errConstExprExpected)
|
|
proc semConceptBody(c: PContext, n: PNode): PNode
|
|
|
|
include semtypes
|
|
|
|
proc setGenericParamsMisc(c: PContext; n: PNode) =
|
|
## used by call defs (procs, templates, macros, ...) to analyse their generic
|
|
## params, and store the originals in miscPos for better error reporting.
|
|
let orig = n[genericParamsPos]
|
|
|
|
doAssert orig.kind in {nkEmpty, nkGenericParams}
|
|
|
|
if n[genericParamsPos].kind == nkEmpty:
|
|
n[genericParamsPos] = newNodeI(nkGenericParams, n.info)
|
|
else:
|
|
# we keep the original params around for better error messages, see
|
|
# issue https://github.com/nim-lang/Nim/issues/1713
|
|
n[genericParamsPos] = semGenericParamList(c, orig)
|
|
|
|
if n[miscPos].kind == nkEmpty:
|
|
n[miscPos] = newTree(nkBracket, c.graph.emptyNode, orig)
|
|
else:
|
|
n[miscPos][1] = orig
|
|
|
|
proc caseBranchMatchesExpr(branch, matched: PNode): bool =
|
|
result = false
|
|
for i in 0 ..< branch.len-1:
|
|
if branch[i].kind == nkRange:
|
|
if overlap(branch[i], matched): return true
|
|
elif exprStructuralEquivalent(branch[i], matched):
|
|
return true
|
|
|
|
proc pickCaseBranchIndex(caseExpr, matched: PNode): int =
|
|
result = 0
|
|
let endsWithElse = caseExpr[^1].kind == nkElse
|
|
for i in 1..<caseExpr.len - endsWithElse.int:
|
|
if caseExpr[i].caseBranchMatchesExpr(matched):
|
|
return i
|
|
if endsWithElse:
|
|
return caseExpr.len - 1
|
|
|
|
proc defaultFieldsForTheUninitialized(c: PContext, recNode: PNode, checkDefault: bool): seq[PNode]
|
|
proc defaultNodeField(c: PContext, a: PNode, aTyp: PType, checkDefault: bool): PNode
|
|
proc defaultNodeField(c: PContext, a: PNode, checkDefault: bool): PNode
|
|
|
|
const defaultFieldsSkipTypes = {tyGenericInst, tyAlias, tySink}
|
|
|
|
proc defaultFieldsForTuple(c: PContext, recNode: PNode, hasDefault: var bool, checkDefault: bool): seq[PNode] =
|
|
result = @[]
|
|
case recNode.kind
|
|
of nkRecList:
|
|
for field in recNode:
|
|
result.add defaultFieldsForTuple(c, field, hasDefault, checkDefault)
|
|
of nkSym:
|
|
let field = recNode.sym
|
|
let recType = recNode.typ.skipTypes(defaultFieldsSkipTypes)
|
|
if field.ast != nil: #Try to use default value
|
|
hasDefault = true
|
|
result.add newTree(nkExprColonExpr, recNode, field.ast)
|
|
else:
|
|
if recType.kind in {tyObject, tyArray, tyTuple}:
|
|
let asgnExpr = defaultNodeField(c, recNode, recNode.typ, checkDefault)
|
|
if asgnExpr != nil:
|
|
hasDefault = true
|
|
asgnExpr.flags.incl nfSkipFieldChecking
|
|
result.add newTree(nkExprColonExpr, recNode, asgnExpr)
|
|
return
|
|
|
|
let asgnType = newType(tyTypeDesc, c.idgen, recNode.typ.owner)
|
|
rawAddSon(asgnType, recNode.typ)
|
|
let asgnExpr = newTree(nkCall,
|
|
newSymNode(getSysMagic(c.graph, recNode.info, "zeroDefault", mZeroDefault)),
|
|
newNodeIT(nkType, recNode.info, asgnType)
|
|
)
|
|
asgnExpr.flags.incl nfSkipFieldChecking
|
|
asgnExpr.typ = recNode.typ
|
|
result.add newTree(nkExprColonExpr, recNode, asgnExpr)
|
|
else:
|
|
raiseAssert "unreachable"
|
|
|
|
proc defaultFieldsForTheUninitialized(c: PContext, recNode: PNode, checkDefault: bool): seq[PNode] =
|
|
result = @[]
|
|
case recNode.kind
|
|
of nkRecList:
|
|
for field in recNode:
|
|
result.add defaultFieldsForTheUninitialized(c, field, checkDefault)
|
|
of nkRecCase:
|
|
let discriminator = recNode[0]
|
|
var selectedBranch: int
|
|
var defaultValue = discriminator.sym.ast
|
|
if defaultValue == nil:
|
|
# None of the branches were explicitly selected by the user and no value
|
|
# was given to the discrimator. We can assume that it will be initialized
|
|
# to zero and this will select a particular branch as a result:
|
|
if checkDefault: # don't add defaults when checking whether a case branch has default fields
|
|
return
|
|
defaultValue = newIntNode(nkIntLit#[c.graph]#, 0)
|
|
defaultValue.typ = discriminator.typ
|
|
selectedBranch = recNode.pickCaseBranchIndex defaultValue
|
|
defaultValue.flags.incl nfSkipFieldChecking
|
|
result.add newTree(nkExprColonExpr, discriminator, defaultValue)
|
|
result.add defaultFieldsForTheUninitialized(c, recNode[selectedBranch][^1], checkDefault)
|
|
of nkSym:
|
|
let field = recNode.sym
|
|
let recType = recNode.typ.skipTypes(defaultFieldsSkipTypes)
|
|
if field.ast != nil: #Try to use default value
|
|
result.add newTree(nkExprColonExpr, recNode, field.ast)
|
|
elif recType.kind in {tyObject, tyArray, tyTuple}:
|
|
let asgnExpr = defaultNodeField(c, recNode, recNode.typ, checkDefault)
|
|
if asgnExpr != nil:
|
|
asgnExpr.typ = recNode.typ
|
|
asgnExpr.flags.incl nfSkipFieldChecking
|
|
result.add newTree(nkExprColonExpr, recNode, asgnExpr)
|
|
else:
|
|
raiseAssert "unreachable"
|
|
|
|
proc defaultNodeField(c: PContext, a: PNode, aTyp: PType, checkDefault: bool): PNode =
|
|
let aTypSkip = aTyp.skipTypes(defaultFieldsSkipTypes)
|
|
case aTypSkip.kind
|
|
of tyObject:
|
|
let child = defaultFieldsForTheUninitialized(c, aTypSkip.n, checkDefault)
|
|
if child.len > 0:
|
|
var asgnExpr = newTree(nkObjConstr, newNodeIT(nkType, a.info, aTyp))
|
|
asgnExpr.typ = aTyp
|
|
asgnExpr.sons.add child
|
|
result = semExpr(c, asgnExpr)
|
|
else:
|
|
result = nil
|
|
of tyArray:
|
|
let child = defaultNodeField(c, a, aTypSkip[1], checkDefault)
|
|
|
|
if child != nil:
|
|
let node = newNode(nkIntLit)
|
|
node.intVal = toInt64(lengthOrd(c.graph.config, aTypSkip))
|
|
result = semExpr(c, newTree(nkCall, newSymNode(getSysSym(c.graph, a.info, "arrayWith"), a.info),
|
|
semExprWithType(c, child),
|
|
node
|
|
))
|
|
result.typ = aTyp
|
|
else:
|
|
result = nil
|
|
of tyTuple:
|
|
var hasDefault = false
|
|
if aTypSkip.n != nil:
|
|
let children = defaultFieldsForTuple(c, aTypSkip.n, hasDefault, checkDefault)
|
|
if hasDefault and children.len > 0:
|
|
result = newNodeI(nkTupleConstr, a.info)
|
|
result.typ = aTyp
|
|
result.sons.add children
|
|
result = semExpr(c, result)
|
|
else:
|
|
result = nil
|
|
else:
|
|
result = nil
|
|
of tyRange:
|
|
if c.graph.config.isDefined("nimPreviewRangeDefault"):
|
|
result = firstRange(c.config, aTypSkip)
|
|
else:
|
|
result = nil
|
|
else:
|
|
result = nil
|
|
|
|
proc defaultNodeField(c: PContext, a: PNode, checkDefault: bool): PNode =
|
|
result = defaultNodeField(c, a, a.typ, checkDefault)
|
|
|
|
include semtempl, semgnrc, semstmts, semexprs
|
|
|
|
proc addCodeForGenerics(c: PContext, n: PNode) =
|
|
for i in c.lastGenericIdx..<c.generics.len:
|
|
var prc = c.generics[i].inst.sym
|
|
if prc.kind in {skProc, skFunc, skMethod, skConverter} and prc.magic == mNone:
|
|
if prc.ast == nil or prc.ast[bodyPos] == nil:
|
|
internalError(c.config, prc.info, "no code for " & prc.name.s)
|
|
else:
|
|
n.add prc.ast
|
|
c.lastGenericIdx = c.generics.len
|
|
|
|
proc preparePContext*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PContext =
|
|
result = newContext(graph, module)
|
|
result.idgen = idgen
|
|
result.enforceVoidContext = newType(tyTyped, idgen, nil)
|
|
result.voidType = newType(tyVoid, idgen, nil)
|
|
|
|
if result.p != nil: internalError(graph.config, module.info, "sem.preparePContext")
|
|
result.semConstExpr = semConstExpr
|
|
result.semExpr = semExpr
|
|
result.semExprWithType = semExprWithType
|
|
result.semTryExpr = tryExpr
|
|
result.semTryConstExpr = tryConstExpr
|
|
result.computeRequiresInit = computeRequiresInit
|
|
result.semOperand = semOperand
|
|
result.semConstBoolExpr = semConstBoolExpr
|
|
result.semOverloadedCall = semOverloadedCall
|
|
result.semInferredLambda = semInferredLambda
|
|
result.semGenerateInstance = generateInstance
|
|
result.instantiateOnlyProcType = instantiateOnlyProcType
|
|
result.semTypeNode = semTypeNode
|
|
result.instTypeBoundOp = sigmatch.instTypeBoundOp
|
|
result.hasUnresolvedArgs = hasUnresolvedArgs
|
|
result.templInstCounter = new int
|
|
|
|
pushProcCon(result, module)
|
|
pushOwner(result, result.module)
|
|
|
|
result.moduleScope = openScope(result)
|
|
result.moduleScope.addSym(module) # a module knows itself
|
|
|
|
if sfSystemModule in module.flags:
|
|
graph.systemModule = module
|
|
result.topLevelScope = openScope(result)
|
|
|
|
proc isImportSystemStmt(g: ModuleGraph; n: PNode): bool =
|
|
if g.systemModule == nil: return false
|
|
var n = n
|
|
if n.kind == nkStmtList:
|
|
for i in 0..<n.len-1:
|
|
if n[i].kind notin {nkCommentStmt, nkEmpty}:
|
|
n = n[i]
|
|
break
|
|
case n.kind
|
|
of nkImportStmt:
|
|
result = false
|
|
for x in n:
|
|
if x.kind == nkIdent:
|
|
let f = checkModuleName(g.config, x, false)
|
|
if f == g.systemModule.info.fileIndex:
|
|
return true
|
|
of nkImportExceptStmt, nkFromStmt:
|
|
result = false
|
|
if n[0].kind == nkIdent:
|
|
let f = checkModuleName(g.config, n[0], false)
|
|
if f == g.systemModule.info.fileIndex:
|
|
return true
|
|
else: result = false
|
|
|
|
proc isEmptyTree(n: PNode): bool =
|
|
case n.kind
|
|
of nkStmtList:
|
|
for it in n:
|
|
if not isEmptyTree(it): return false
|
|
result = true
|
|
of nkEmpty, nkCommentStmt: result = true
|
|
else: result = false
|
|
|
|
proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
|
|
if c.topStmts == 0 and not isImportSystemStmt(c.graph, n):
|
|
if sfSystemModule notin c.module.flags and not isEmptyTree(n):
|
|
assert c.graph.systemModule != nil
|
|
c.moduleScope.addSym c.graph.systemModule # import the "System" identifier
|
|
importAllSymbols(c, c.graph.systemModule)
|
|
inc c.topStmts
|
|
else:
|
|
inc c.topStmts
|
|
if sfNoForward in c.module.flags:
|
|
result = semAllTypeSections(c, n)
|
|
else:
|
|
result = n
|
|
result = semStmt(c, result, {})
|
|
when false:
|
|
# Code generators are lazy now and can deal with undeclared procs, so these
|
|
# steps are not required anymore and actually harmful for the upcoming
|
|
# destructor support.
|
|
# BUGFIX: process newly generated generics here, not at the end!
|
|
if c.lastGenericIdx < c.generics.len:
|
|
var a = newNodeI(nkStmtList, n.info)
|
|
addCodeForGenerics(c, a)
|
|
if a.len > 0:
|
|
# a generic has been added to `a`:
|
|
if result.kind != nkEmpty: a.add result
|
|
result = a
|
|
result = hloStmt(c, result)
|
|
if c.config.cmd == cmdInteractive and not isEmptyType(result.typ):
|
|
result = buildEchoStmt(c, result)
|
|
if c.config.cmd == cmdIdeTools:
|
|
appendToModule(c.module, result)
|
|
trackStmt(c, c.module, result, isTopLevel = true)
|
|
if optMultiMethods notin c.config.globalOptions and
|
|
c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
|
|
Feature.vtables in c.config.features:
|
|
sortVTableDispatchers(c.graph)
|
|
|
|
if sfMainModule in c.module.flags:
|
|
collectVTableDispatchers(c.graph)
|
|
|
|
proc recoverContext(c: PContext) =
|
|
# clean up in case of a semantic error: We clean up the stacks, etc. This is
|
|
# faster than wrapping every stack operation in a 'try finally' block and
|
|
# requires far less code.
|
|
c.currentScope = c.topLevelScope
|
|
while getCurrOwner(c).kind != skModule: popOwner(c)
|
|
while c.p != nil and c.p.owner.kind != skModule: c.p = c.p.next
|
|
|
|
proc semWithPContext*(c: PContext, n: PNode): PNode =
|
|
# no need for an expensive 'try' if we stop after the first error anyway:
|
|
if c.config.errorMax <= 1:
|
|
result = semStmtAndGenerateGenerics(c, n)
|
|
else:
|
|
let oldContextLen = msgs.getInfoContextLen(c.config)
|
|
let oldInGenericInst = c.inGenericInst
|
|
try:
|
|
result = semStmtAndGenerateGenerics(c, n)
|
|
except ERecoverableError, ESuggestDone:
|
|
recoverContext(c)
|
|
c.inGenericInst = oldInGenericInst
|
|
msgs.setInfoContextLen(c.config, oldContextLen)
|
|
if getCurrentException() of ESuggestDone:
|
|
c.suggestionsMade = true
|
|
result = nil
|
|
else:
|
|
result = newNodeI(nkEmpty, n.info)
|
|
#if c.config.cmd == cmdIdeTools: findSuggest(c, n)
|
|
storeRodNode(c, result)
|
|
|
|
|
|
proc reportUnusedModules(c: PContext) =
|
|
if c.config.cmd == cmdM: return
|
|
for i in 0..high(c.unusedImports):
|
|
if sfUsed notin c.unusedImports[i][0].flags:
|
|
message(c.config, c.unusedImports[i][1], warnUnusedImportX, c.unusedImports[i][0].name.s)
|
|
|
|
proc closePContext*(graph: ModuleGraph; c: PContext, n: PNode): PNode =
|
|
if c.config.cmd == cmdIdeTools and not c.suggestionsMade:
|
|
suggestSentinel(c)
|
|
closeScope(c) # close module's scope
|
|
rawCloseScope(c) # imported symbols; don't check for unused ones!
|
|
reportUnusedModules(c)
|
|
result = newNode(nkStmtList)
|
|
if n != nil:
|
|
internalError(c.config, n.info, "n is not nil") #result := n;
|
|
addCodeForGenerics(c, result)
|
|
if c.module.ast != nil:
|
|
result.add(c.module.ast)
|
|
popOwner(c)
|
|
popProcCon(c)
|
|
sealRodFile(c)
|