#
#
#           The Nim Compiler
#        (c) Copyright 2017 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module contains the data structures for the semantic checking phase.

import std/[tables, intsets, sets, strutils]

when defined(nimPreviewSlimSystem):
  import std/assertions

import
  options, ast, msgs, idents, renderer,
  magicsys, vmdef, modulegraphs, lineinfos, pathutils, layeredtable,
  types, lowerings, trees, parampatterns, astalgo

import ic / ic

type
  TOptionEntry* = object      # entries to put on a stack for pragma parsing
    options*: TOptions
    defaultCC*: TCallingConvention
    dynlib*: PLib
    notes*: TNoteKinds
    features*: set[Feature]
    otherPragmas*: PNode      # every pragma can be pushed
    warningAsErrors*: TNoteKinds

  POptionEntry* = ref TOptionEntry
  PProcCon* = ref TProcCon
  TProcCon* {.acyclic.} = object # procedure context; also used for top-level
                                 # statements
    owner*: PSym              # the symbol this context belongs to
    resultSym*: PSym          # the result symbol (if we are in a proc)
    nestedLoopCounter*: int   # whether we are in a loop or not
    nestedBlockCounter*: int  # whether we are in a block or not
    breakInLoop*: bool        # whether we are in a loop without block
    next*: PProcCon           # used for stacking procedure contexts
    mappingExists*: bool
    mapping*: SymMapping
    caseContext*: seq[tuple[n: PNode, idx: int]]
    localBindStmts*: seq[PNode]

  TMatchedConcept* = object
    candidateType*: PType
    prev*: ptr TMatchedConcept
    depth*: int

  TInstantiationPair* = object
    genericSym*: PSym
    inst*: PInstantiation

  TExprFlag* = enum
    efLValue, efWantIterator, efWantIterable, efInTypeof,
    efNeedStatic,
      # Use this in contexts where a static value is mandatory
    efPreferStatic,
      # Use this in contexts where a static value could bring more
      # information, but it's not strictly mandatory. This may become
      # the default with implicit statics in the future.
    efPreferNilResult,
      # Use this if you want a certain result (e.g. static value),
      # but you don't want to trigger a hard error. For example,
      # you may be in position to supply a better error message
      # to the user.
    efWantStmt, efAllowStmt, efDetermineType, efExplain,
    efWantValue, efOperand, efNoSemCheck,
    efNoEvaluateGeneric, efInCall, efFromHlo, efNoSem2Check,
    efNoUndeclared, efIsDotCall, efCannotBeDotCall,
      # Use this if undeclared identifiers should not raise an error during
      # overload resolution.
    efTypeAllowed # typeAllowed will be called after
    efWantNoDefaults
    efIgnoreDefaults # var statements without initialization
    efAllowSymChoice # symchoice node should not be resolved

  TExprFlags* = set[TExprFlag]

  ImportMode* = enum
    importAll, importSet, importExcept
  ImportedModule* = object
    m*: PSym
    case mode*: ImportMode
    of importAll: discard
    of importSet:
      imported*: IntSet          # of PIdent.id
    of importExcept:
      exceptSet*: IntSet         # of PIdent.id

  PContext* = ref TContext
  TContext* = object of TPassContext # a context represents the module
                                     # that is currently being compiled
    enforceVoidContext*: PType
      # for `if cond: stmt else: foo`, `foo` will be evaluated under
      # enforceVoidContext != nil
    voidType*: PType # for typeof(stmt)
    module*: PSym              # the module sym belonging to the context
    currentScope*: PScope      # current scope
    moduleScope*: PScope       # scope for modules
    imports*: seq[ImportedModule] # scope for all imported symbols
    topLevelScope*: PScope     # scope for all top-level symbols
    p*: PProcCon               # procedure context
    intTypeCache*: array[-5..32, PType] # cache some common integer types
                                        # to avoid type allocations
    nilTypeCache*: PType
    matchedConcept*: ptr TMatchedConcept # the current concept being matched
    friendModules*: seq[PSym]  # friend modules; may access private data;
                               # this is used so that generic instantiations
                               # can access private object fields
    instCounter*: int          # to prevent endless instantiations
    templInstCounter*: ref int # gives every template instantiation a unique id
    inGenericContext*: int     # > 0 if we are in a generic type
    inStaticContext*: int      # > 0 if we are inside a static: block
    inUnrolledContext*: int    # > 0 if we are unrolling a loop
    compilesContextId*: int    # > 0 if we are in a ``compiles`` magic
    compilesContextIdGenerator*: int
    inGenericInst*: int        # > 0 if we are instantiating a generic
    converters*: seq[PSym]
    patterns*: seq[PSym]       # sequence of pattern matchers
    optionStack*: seq[POptionEntry]
    libs*: seq[PLib]           # all libs used by this module
    semConstExpr*: proc (c: PContext, n: PNode; expectedType: PType = nil): PNode {.nimcall.} # for the pragmas
    semExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode {.nimcall.}
    semExprWithType*: proc (c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode {.nimcall.}
    semTryExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
    semTryConstExpr*: proc (c: PContext, n: PNode; expectedType: PType = nil): PNode {.nimcall.}
    computeRequiresInit*: proc (c: PContext, t: PType): bool {.nimcall.}
    hasUnresolvedArgs*: proc (c: PContext, n: PNode): bool

    semOperand*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
    semConstBoolExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # XXX bite the bullet
    semOverloadedCall*: proc (c: PContext, n, nOrig: PNode,
                              filter: TSymKinds, flags: TExprFlags, expectedType: PType = nil): PNode {.nimcall.}
    semTypeNode*: proc(c: PContext, n: PNode, prev: PType): PType {.nimcall.}
    semInferredLambda*: proc(c: PContext, pt: LayeredIdTable, n: PNode): PNode
    semGenerateInstance*: proc (c: PContext, fn: PSym, pt: LayeredIdTable,
                                info: TLineInfo): PSym
    instantiateOnlyProcType*: proc (c: PContext, pt: LayeredIdTable,
                                    prc: PSym, info: TLineInfo): PType
      # used by sigmatch for explicit generic instantiations
    fitDefaultNode*: proc (c: PContext, n: var PNode, expectedType: PType)
    includedFiles*: IntSet    # used to detect recursive include files
    pureEnumFields*: TStrTable   # pure enum fields that can be used unambiguously
    userPragmas*: TStrTable
    evalContext*: PEvalContext
    unknownIdents*: IntSet     # ids of all unknown identifiers to prevent
                               # naming it multiple times
    generics*: seq[TInstantiationPair] # pending list of instantiated generics to compile
    topStmts*: int # counts the number of encountered top level statements
    lastGenericIdx*: int      # used for the generics stack
    hloLoopDetector*: int     # used to prevent endless loops in the HLO
    inParallelStmt*: int
    instTypeBoundOp*: proc (c: PContext; dc: PSym; t: PType; info: TLineInfo;
                            op: TTypeAttachedOp; col: int): PSym {.nimcall.}
    cache*: IdentCache
    graph*: ModuleGraph
    signatures*: TStrTable
    recursiveDep*: string
    suggestionsMade*: bool
    isAmbiguous*: bool # little hack
    features*: set[Feature]
    inTypeContext*, inConceptDecl*: int
    unusedImports*: seq[(PSym, TLineInfo)]
    exportIndirections*: HashSet[(int, int)] # (module.id, symbol.id)
    importModuleMap*: Table[int, int] # (module.id, module.id)
    lastTLineInfo*: TLineInfo
    sideEffects*: Table[int, seq[(TLineInfo, PSym)]] # symbol.id index
    inUncheckedAssignSection*: int
    importModuleLookup*: Table[int, seq[int]] # (module.ident.id, [module.id])
    skipTypes*: seq[PNode] # used to skip types between passes in type section. So far only used for inheritance, sets and generic bodies.
    inTypeofContext*: int

    semAsgnOpr*: proc (c: PContext; n: PNode; k: TNodeKind): PNode {.nimcall.}

  TBorrowState* = enum
    bsNone, bsReturnNotMatch, bsNoDistinct, bsGeneric, bsNotSupported, bsMatch

template config*(c: PContext): ConfigRef = c.graph.config

proc getIntLitType*(c: PContext; literal: PNode): PType =
  # we cache some common integer literal types for performance:
  let value = literal.intVal
  if value >= low(c.intTypeCache) and value <= high(c.intTypeCache):
    result = c.intTypeCache[value.int]
    if result == nil:
      let ti = getSysType(c.graph, literal.info, tyInt)
      result = copyType(ti, c.idgen, ti.owner)
      result.n = literal
      c.intTypeCache[value.int] = result
  else:
    let ti = getSysType(c.graph, literal.info, tyInt)
    result = copyType(ti, c.idgen, ti.owner)
    result.n = literal

proc setIntLitType*(c: PContext; result: PNode) =
  let i = result.intVal
  case c.config.target.intSize
  of 8: result.typ() = getIntLitType(c, result)
  of 4:
    if i >= low(int32) and i <= high(int32):
      result.typ() = getIntLitType(c, result)
    else:
      result.typ() = getSysType(c.graph, result.info, tyInt64)
  of 2:
    if i >= low(int16) and i <= high(int16):
      result.typ() = getIntLitType(c, result)
    elif i >= low(int32) and i <= high(int32):
      result.typ() = getSysType(c.graph, result.info, tyInt32)
    else:
      result.typ() = getSysType(c.graph, result.info, tyInt64)
  of 1:
    # 8 bit CPUs are insane ...
    if i >= low(int8) and i <= high(int8):
      result.typ() = getIntLitType(c, result)
    elif i >= low(int16) and i <= high(int16):
      result.typ() = getSysType(c.graph, result.info, tyInt16)
    elif i >= low(int32) and i <= high(int32):
      result.typ() = getSysType(c.graph, result.info, tyInt32)
    else:
      result.typ() = getSysType(c.graph, result.info, tyInt64)
  else:
    internalError(c.config, result.info, "invalid int size")

proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair =
  result = TInstantiationPair(genericSym: s, inst: inst)

proc filename*(c: PContext): string =
  # the module's filename
  result = toFilename(c.config, FileIndex c.module.position)

proc scopeDepth*(c: PContext): int {.inline.} =
  result = if c.currentScope != nil: c.currentScope.depthLevel
           else: 0

proc getCurrOwner*(c: PContext): PSym =
  # owner stack (used for initializing the
  # owner field of syms)
  # the documentation comment always gets
  # assigned to the current owner
  result = c.graph.owners[^1]

proc pushOwner*(c: PContext; owner: PSym) =
  c.graph.owners.add(owner)

proc popOwner*(c: PContext) =
  if c.graph.owners.len > 0: setLen(c.graph.owners, c.graph.owners.len - 1)
  else: internalError(c.config, "popOwner")

proc lastOptionEntry*(c: PContext): POptionEntry =
  result = c.optionStack[^1]

proc popProcCon*(c: PContext) {.inline.} = c.p = c.p.next

proc put*(p: PProcCon; key, val: PSym) =
  if not p.mappingExists:
    p.mapping = initSymMapping()
    p.mappingExists = true
  #echo "put into table ", key.info
  p.mapping[key.itemId] = val

proc get*(p: PProcCon; key: PSym): PSym =
  if not p.mappingExists: return nil
  result = p.mapping.getOrDefault(key.itemId)

proc getGenSym*(c: PContext; s: PSym): PSym =
  if sfGenSym notin s.flags: return s
  var it = c.p
  while it != nil:
    result = get(it, s)
    if result != nil:
      #echo "got from table ", result.name.s, " ", result.info
      return result
    it = it.next
  result = s

proc considerGenSyms*(c: PContext; n: PNode) =
  if n == nil:
    discard "can happen for nkFormalParams/nkArgList"
  elif n.kind == nkSym:
    let s = getGenSym(c, n.sym)
    if n.sym != s:
      n.sym = s
  else:
    for i in 0..<n.safeLen:
      considerGenSyms(c, n[i])

proc newOptionEntry*(conf: ConfigRef): POptionEntry =
  result = POptionEntry(
    options: conf.options,
    defaultCC: ccNimCall,
    dynlib: nil,
    notes: conf.notes,
    warningAsErrors: conf.warningAsErrors
  )

proc pushOptionEntry*(c: PContext): POptionEntry =
  let prev = c.optionStack[^1]
  result = POptionEntry(
    options: c.config.options,
    defaultCC: prev.defaultCC,
    dynlib: prev.dynlib,
    notes: c.config.notes,
    warningAsErrors: c.config.warningAsErrors,
    features: c.features
  )
  c.optionStack.add(result)

proc popOptionEntry*(c: PContext) =
  c.config.options = c.optionStack[^1].options
  c.config.notes = c.optionStack[^1].notes
  c.config.warningAsErrors = c.optionStack[^1].warningAsErrors
  c.features = c.optionStack[^1].features
  c.optionStack.setLen(c.optionStack.len - 1)

proc newContext*(graph: ModuleGraph; module: PSym): PContext =
  result = PContext(
    optionStack: @[newOptionEntry(graph.config)],
    libs: @[],
    module: module,
    friendModules: @[module],
    converters: @[],
    patterns: @[],
    includedFiles: initIntSet(),
    pureEnumFields: initStrTable(),
    userPragmas: initStrTable(),
    generics: @[],
    unknownIdents: initIntSet(),
    cache: graph.cache,
    graph: graph,
    signatures: initStrTable(),
    features: graph.config.features
  )
  if graph.config.symbolFiles != disabledSf:
    let id = module.position
    if graph.config.cmd != cmdM:
      assert graph.packed[id].status in {undefined, outdated}
    graph.packed[id].status = storing
    graph.packed[id].module = module
    initEncoder graph, module

template packedRepr*(c): untyped = c.graph.packed[c.module.position].fromDisk
template encoder*(c): untyped = c.graph.encoders[c.module.position]

proc addIncludeFileDep*(c: PContext; f: FileIndex) =
  if c.config.symbolFiles != disabledSf:
    addIncludeFileDep(c.encoder, c.packedRepr, f)

proc addImportFileDep*(c: PContext; f: FileIndex) =
  if c.config.symbolFiles != disabledSf:
    addImportFileDep(c.encoder, c.packedRepr, f)

proc addPragmaComputation*(c: PContext; n: PNode) =
  if c.config.symbolFiles != disabledSf:
    addPragmaComputation(c.encoder, c.packedRepr, n)

proc inclSym(sq: var seq[PSym], s: PSym): bool =
  for i in 0..<sq.len:
    if sq[i].id == s.id: return false
  sq.add s
  result = true

proc addConverter*(c: PContext, conv: LazySym) =
  assert conv.sym != nil
  if inclSym(c.converters, conv.sym):
    add(c.graph.ifaces[c.module.position].converters, conv)

proc addConverterDef*(c: PContext, conv: LazySym) =
  addConverter(c, conv)
  if c.config.symbolFiles != disabledSf:
    addConverter(c.encoder, c.packedRepr, conv.sym)

proc addPureEnum*(c: PContext, e: LazySym) =
  assert e.sym != nil
  add(c.graph.ifaces[c.module.position].pureEnums, e)
  if c.config.symbolFiles != disabledSf:
    addPureEnum(c.encoder, c.packedRepr, e.sym)

proc addPattern*(c: PContext, p: LazySym) =
  assert p.sym != nil
  if inclSym(c.patterns, p.sym):
    add(c.graph.ifaces[c.module.position].patterns, p)
  if c.config.symbolFiles != disabledSf:
    addTrmacro(c.encoder, c.packedRepr, p.sym)

proc exportSym*(c: PContext; s: PSym) =
  strTableAdds(c.graph, c.module, s)
  if c.config.symbolFiles != disabledSf:
    addExported(c.encoder, c.packedRepr, s)

proc reexportSym*(c: PContext; s: PSym) =
  strTableAdds(c.graph, c.module, s)
  if c.config.symbolFiles != disabledSf:
    addReexport(c.encoder, c.packedRepr, s)

proc newLib*(kind: TLibKind): PLib =
  result = PLib(kind: kind)   #result.syms = initObjectSet()

proc addToLib*(lib: PLib, sym: PSym) =
  #if sym.annex != nil and not isGenericRoutine(sym):
  #  LocalError(sym.info, errInvalidPragma)
  sym.annex = lib

proc newTypeS*(kind: TTypeKind; c: PContext; son: sink PType = nil): PType =
  result = newType(kind, c.idgen, getCurrOwner(c), son = son)

proc makePtrType*(owner: PSym, baseType: PType; idgen: IdGenerator): PType =
  result = newType(tyPtr, idgen, owner, skipIntLit(baseType, idgen))

proc makePtrType*(c: PContext, baseType: PType): PType =
  makePtrType(getCurrOwner(c), baseType, c.idgen)

proc makeTypeWithModifier*(c: PContext,
                           modifier: TTypeKind,
                           baseType: PType): PType =
  assert modifier in {tyVar, tyLent, tyPtr, tyRef, tyStatic, tyTypeDesc}

  if modifier in {tyVar, tyLent, tyTypeDesc} and baseType.kind == modifier:
    result = baseType
  else:
    result = newTypeS(modifier, c, skipIntLit(baseType, c.idgen))

proc makeVarType*(c: PContext, baseType: PType; kind = tyVar): PType =
  if baseType.kind == kind:
    result = baseType
  else:
    result = newTypeS(kind, c, skipIntLit(baseType, c.idgen))

proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode =
  let typedesc = newTypeS(tyTypeDesc, c)
  incl typedesc.flags, tfCheckedForDestructor
  internalAssert(c.config, typ != nil)
  typedesc.addSonSkipIntLit(typ, c.idgen)
  let sym = newSym(skType, c.cache.idAnon, c.idgen, getCurrOwner(c), info,
                   c.config.options).linkTo(typedesc)
  result = newSymNode(sym, info)

proc makeTypeFromExpr*(c: PContext, n: PNode): PType =
  result = newTypeS(tyFromExpr, c)
  assert n != nil
  result.n = n

when false:
  proc newTypeWithSons*(owner: PSym, kind: TTypeKind, sons: seq[PType];
                        idgen: IdGenerator): PType =
    result = newType(kind, idgen, owner, sons = sons)

  proc newTypeWithSons*(c: PContext, kind: TTypeKind,
                        sons: seq[PType]): PType =
    result = newType(kind, c.idgen, getCurrOwner(c), sons = sons)

proc makeStaticExpr*(c: PContext, n: PNode): PNode =
  result = newNodeI(nkStaticExpr, n.info)
  result.sons = @[n]
  result.typ() = if n.typ != nil and n.typ.kind == tyStatic: n.typ
               else: newTypeS(tyStatic, c, n.typ)

proc makeAndType*(c: PContext, t1, t2: PType): PType =
  result = newTypeS(tyAnd, c)
  result.rawAddSon t1
  result.rawAddSon t2
  propagateToOwner(result, t1)
  propagateToOwner(result, t2)
  result.flags.incl((t1.flags + t2.flags) * {tfHasStatic})
  result.flags.incl tfHasMeta

proc makeOrType*(c: PContext, t1, t2: PType): PType =
  if t1.kind != tyOr and t2.kind != tyOr:
    result = newTypeS(tyOr, c)
    result.rawAddSon t1
    result.rawAddSon t2
  else:
    result = newTypeS(tyOr, c)
    template addOr(t1) =
      if t1.kind == tyOr:
        for x in t1.kids: result.rawAddSon x
      else:
        result.rawAddSon t1
    addOr(t1)
    addOr(t2)
  propagateToOwner(result, t1)
  propagateToOwner(result, t2)
  result.flags.incl((t1.flags + t2.flags) * {tfHasStatic})
  result.flags.incl tfHasMeta

proc makeNotType*(c: PContext, t1: PType): PType =
  result = newTypeS(tyNot, c, son = t1)
  propagateToOwner(result, t1)
  result.flags.incl(t1.flags * {tfHasStatic})
  result.flags.incl tfHasMeta

proc nMinusOne(c: PContext; n: PNode): PNode =
  result = newTreeI(nkCall, n.info, newSymNode(getSysMagic(c.graph, n.info, "pred", mPred)), n)

# Remember to fix the procs below this one when you make changes!
proc makeRangeWithStaticExpr*(c: PContext, n: PNode): PType =
  let intType = getSysType(c.graph, n.info, tyInt)
  result = newTypeS(tyRange, c, son = intType)
  if n.typ != nil and n.typ.n == nil:
    result.flags.incl tfUnresolved
  result.n = newTreeI(nkRange, n.info, newIntTypeNode(0, intType),
    makeStaticExpr(c, nMinusOne(c, n)))

template rangeHasUnresolvedStatic*(t: PType): bool =
  tfUnresolved in t.flags

proc errorType*(c: PContext): PType =
  ## creates a type representing an error state
  result = newTypeS(tyError, c)
  result.flags.incl tfCheckedForDestructor

proc errorNode*(c: PContext, n: PNode): PNode =
  result = newNodeI(nkEmpty, n.info)
  result.typ() = errorType(c)

# These mimic localError
template localErrorNode*(c: PContext, n: PNode, info: TLineInfo, msg: TMsgKind, arg: string): PNode =
  liMessage(c.config, info, msg, arg, doNothing, instLoc())
  errorNode(c, n)

template localErrorNode*(c: PContext, n: PNode, info: TLineInfo, arg: string): PNode =
  liMessage(c.config, info, errGenerated, arg, doNothing, instLoc())
  errorNode(c, n)

template localErrorNode*(c: PContext, n: PNode, msg: TMsgKind, arg: string): PNode =
  let n2 = n
  liMessage(c.config, n2.info, msg, arg, doNothing, instLoc())
  errorNode(c, n2)

template localErrorNode*(c: PContext, n: PNode, arg: string): PNode =
  let n2 = n
  liMessage(c.config, n2.info, errGenerated, arg, doNothing, instLoc())
  errorNode(c, n2)

when false:
  proc fillTypeS*(dest: PType, kind: TTypeKind, c: PContext) =
    dest.kind = kind
    dest.owner = getCurrOwner(c)
    dest.size = - 1

proc makeRangeType*(c: PContext; first, last: BiggestInt;
                    info: TLineInfo; intType: PType = nil): PType =
  let intType = if intType != nil: intType else: getSysType(c.graph, info, tyInt)
  var n = newNodeI(nkRange, info)
  n.add newIntTypeNode(first, intType)
  n.add newIntTypeNode(last, intType)
  result = newTypeS(tyRange, c)
  result.n = n
  addSonSkipIntLit(result, intType, c.idgen) # basetype of range

proc isSelf*(t: PType): bool {.inline.} =
  ## Is this the magical 'Self' type from concepts?
  t.kind == tyTypeDesc and tfPacked in t.flags

proc makeTypeDesc*(c: PContext, typ: PType): PType =
  if typ.kind == tyTypeDesc and not isSelf(typ):
    result = typ
  else:
    result = newTypeS(tyTypeDesc, c, skipIntLit(typ, c.idgen))
    incl result.flags, tfCheckedForDestructor

proc symFromType*(c: PContext; t: PType, info: TLineInfo): PSym =
  if t.sym != nil: return t.sym
  result = newSym(skType, getIdent(c.cache, "AnonType"), c.idgen, t.owner, info)
  result.flags.incl sfAnon
  result.typ = t

proc symNodeFromType*(c: PContext, t: PType, info: TLineInfo): PNode =
  result = newSymNode(symFromType(c, t, info), info)
  result.typ() = makeTypeDesc(c, t)

proc markIndirect*(c: PContext, s: PSym) {.inline.} =
  if s.kind in {skProc, skFunc, skConverter, skMethod, skIterator}:
    incl(s.flags, sfAddrTaken)
    # XXX add to 'c' for global analysis

proc illFormedAst*(n: PNode; conf: ConfigRef) =
  globalError(conf, n.info, errIllFormedAstX, renderTree(n, {renderNoComments}))

proc illFormedAstLocal*(n: PNode; conf: ConfigRef) =
  localError(conf, n.info, errIllFormedAstX, renderTree(n, {renderNoComments}))

proc checkSonsLen*(n: PNode, length: int; conf: ConfigRef) =
  if n.len != length: illFormedAst(n, conf)

proc checkMinSonsLen*(n: PNode, length: int; conf: ConfigRef) =
  if n.len < length: illFormedAst(n, conf)

proc isTopLevel*(c: PContext): bool {.inline.} =
  result = c.currentScope.depthLevel <= 2

proc isTopLevelInsideDeclaration*(c: PContext, sym: PSym): bool {.inline.} =
  # for routeKinds the scope isn't closed yet:
  c.currentScope.depthLevel <= 2 + ord(sym.kind in routineKinds)

proc pushCaseContext*(c: PContext, caseNode: PNode) =
  c.p.caseContext.add((caseNode, 0))

proc popCaseContext*(c: PContext) =
  discard pop(c.p.caseContext)

proc setCaseContextIdx*(c: PContext, idx: int) =
  c.p.caseContext[^1].idx = idx

template addExport*(c: PContext; s: PSym) =
  ## convenience to export a symbol from the current module
  addExport(c.graph, c.module, s)

proc storeRodNode*(c: PContext, n: PNode) =
  if c.config.symbolFiles != disabledSf:
    toPackedNodeTopLevel(n, c.encoder, c.packedRepr)

proc addToGenericProcCache*(c: PContext; s: PSym; inst: PInstantiation) =
  c.graph.procInstCache.mgetOrPut(s.itemId, @[]).add LazyInstantiation(module: c.module.position, inst: inst)
  if c.config.symbolFiles != disabledSf:
    storeInstantiation(c.encoder, c.packedRepr, s, inst)

proc addToGenericCache*(c: PContext; s: PSym; inst: PType) =
  c.graph.typeInstCache.mgetOrPut(s.itemId, @[]).add LazyType(typ: inst)
  if c.config.symbolFiles != disabledSf:
    storeTypeInst(c.encoder, c.packedRepr, s, inst)

proc sealRodFile*(c: PContext) =
  if c.config.symbolFiles != disabledSf:
    if c.graph.vm != nil:
      for (m, n) in PCtx(c.graph.vm).vmstateDiff:
        if m == c.module:
          addPragmaComputation(c, n)
    c.idgen.sealed = true # no further additions are allowed

proc rememberExpansion*(c: PContext; info: TLineInfo; expandedSym: PSym) =
  ## Templates and macros are very special in Nim; these have
  ## inlining semantics so after semantic checking they leave no trace
  ## in the sem'checked AST. This is very bad for IDE-like tooling
  ## ("find all usages of this template" would not work). We need special
  ## logic to remember macro/template expansions. This is done here and
  ## delegated to the "rod" file mechanism.
  if c.config.symbolFiles != disabledSf:
    storeExpansion(c.encoder, c.packedRepr, info, expandedSym)

const
  errVarForOutParamNeededX = "for a 'var' type a variable needs to be passed; but '$1' is immutable"
  errXStackEscape = "address of '$1' may not escape its stack frame"

proc renderNotLValue*(n: PNode): string =
  result = $n
  let n = if n.kind == nkHiddenDeref: n[0] else: n
  if n.kind == nkHiddenCallConv and n.len > 1:
    result = $n[0] & "(" & result & ")"
  elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2:
    result = typeToString(n.typ.skipTypes(abstractVar)) & "(" & result & ")"

proc isAssignable(c: PContext, n: PNode): TAssignableResult =
  result = parampatterns.isAssignable(c.p.owner, n)

proc newHiddenAddrTaken(c: PContext, n: PNode, isOutParam: bool): PNode =
  if n.kind == nkHiddenDeref and not (c.config.backend == backendCpp or
                                      sfCompileToCpp in c.module.flags):
    checkSonsLen(n, 1, c.config)
    result = n[0]
  else:
    result = newNodeIT(nkHiddenAddr, n.info, makeVarType(c, n.typ))
    result.add n
    let aa = isAssignable(c, n)
    let sym = getRoot(n)
    if aa notin {arLValue, arLocalLValue}:
      if aa == arDiscriminant and c.inUncheckedAssignSection > 0:
        discard "allow access within a cast(unsafeAssign) section"
      elif strictDefs in c.features and aa == arAddressableConst and
              sym != nil and sym.kind == skLet and isOutParam:
        discard "allow let varaibles to be passed to out parameters"
      else:
        localError(c.config, n.info, errVarForOutParamNeededX % renderNotLValue(n))

proc analyseIfAddressTaken(c: PContext, n: PNode, isOutParam: bool): PNode =
  result = n
  case n.kind
  of nkSym:
    # n.sym.typ can be nil in 'check' mode ...
    if n.sym.typ != nil and
        skipTypes(n.sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}:
      incl(n.sym.flags, sfAddrTaken)
      result = newHiddenAddrTaken(c, n, isOutParam)
  of nkDotExpr:
    checkSonsLen(n, 2, c.config)
    if n[1].kind != nkSym:
      internalError(c.config, n.info, "analyseIfAddressTaken")
      return
    if skipTypes(n[1].sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}:
      incl(n[1].sym.flags, sfAddrTaken)
      result = newHiddenAddrTaken(c, n, isOutParam)
  of nkBracketExpr:
    checkMinSonsLen(n, 1, c.config)
    if skipTypes(n[0].typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}:
      if n[0].kind == nkSym: incl(n[0].sym.flags, sfAddrTaken)
      result = newHiddenAddrTaken(c, n, isOutParam)
  else:
    result = newHiddenAddrTaken(c, n, isOutParam)

proc analyseIfAddressTakenInCall*(c: PContext, n: PNode, isConverter = false) =
  checkMinSonsLen(n, 1, c.config)
  if n[0].typ == nil:
    # n[0] might be erroring node in nimsuggest
    return
  const
    FakeVarParams = {mNew, mNewFinalize, mInc, ast.mDec, mIncl, mExcl,
      mSetLengthStr, mSetLengthSeq, mAppendStrCh, mAppendStrStr, mSwap,
      mAppendSeqElem, mNewSeq, mShallowCopy, mDeepCopy, mMove, mWasMoved}

  template checkIfConverterCalled(c: PContext, n: PNode) =
    ## Checks if there is a converter call which wouldn't be checked otherwise
    # Call can sometimes be wrapped in a deref
    let node = if n.kind == nkHiddenDeref: n[0] else: n
    if node.kind == nkHiddenCallConv:
      analyseIfAddressTakenInCall(c, node, true)
  # get the real type of the callee
  # it may be a proc var with a generic alias type, so we skip over them
  var t = n[0].typ.skipTypes({tyGenericInst, tyAlias, tySink})
  if n[0].kind == nkSym and n[0].sym.magic in FakeVarParams:
    # BUGFIX: check for L-Value still needs to be done for the arguments!
    # note sometimes this is eval'ed twice so we check for nkHiddenAddr here:
    for i in 1..<n.len:
      if i < t.len and t[i] != nil and
          skipTypes(t[i], abstractInst-{tyTypeDesc}).kind in {tyVar}:
        let it = n[i]
        let aa = isAssignable(c, it)
        if aa notin {arLValue, arLocalLValue}:
          if it.kind != nkHiddenAddr:
            if aa == arDiscriminant and c.inUncheckedAssignSection > 0:
              discard "allow access within a cast(unsafeAssign) section"
            else:
              localError(c.config, it.info, errVarForOutParamNeededX % $it)
        # Make sure to still check arguments for converters
        c.checkIfConverterCalled(n[i])
    # bug #5113: disallow newSeq(result) where result is a 'var T':
    if n[0].sym.magic in {mNew, mNewFinalize, mNewSeq}:
      var arg = n[1] #.skipAddr
      if arg.kind == nkHiddenDeref: arg = arg[0]
      if arg.kind == nkSym and arg.sym.kind == skResult and
          arg.typ.skipTypes(abstractInst).kind in {tyVar, tyLent}:
        localError(c.config, n.info, errXStackEscape % renderTree(n[1], {renderNoComments}))

    return
  for i in 1..<n.len:
    let n = if n.kind == nkHiddenDeref: n[0] else: n
    c.checkIfConverterCalled(n[i])
    if i < t.len and
        skipTypes(t[i], abstractInst-{tyTypeDesc}).kind in {tyVar}:
      # Converters wrap var parameters in nkHiddenAddr but they haven't been analysed yet.
      # So we need to make sure we are checking them still when in a converter call
      if n[i].kind != nkHiddenAddr or isConverter:
        n[i] = analyseIfAddressTaken(c, n[i].skipAddr(), isOutParam(skipTypes(t[i], abstractInst-{tyTypeDesc})))


proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode =
  ## Replaces builtin generic hooks with lifted hooks.
  case kind
  of attachedDestructor:
    result = n
    let t = n[1].typ.skipTypes(abstractVar)
    let op = getAttachedOp(c.graph, t, attachedDestructor)
    if op != nil:
      result[0] = newSymNode(op)
      if op.typ != nil and op.typ.len == 2 and op.typ.firstParamType.kind != tyVar:
        if n[1].kind == nkSym and n[1].sym.kind == skParam and
            n[1].typ.kind == tyVar:
          result[1] = genDeref(n[1])
        else:
          result[1] = skipAddr(n[1])
  of attachedTrace:
    result = n
    let t = n[1].typ.skipTypes(abstractVar)
    let op = getAttachedOp(c.graph, t, attachedTrace)
    if op != nil:
      result[0] = newSymNode(op)
  of attachedDup:
    result = n
    let t = n[1].typ.skipTypes(abstractVar)
    let op = getAttachedOp(c.graph, t, attachedDup)
    if op != nil:
      result[0] = newSymNode(op)
      if op.typ.len == 3:
        let boolLit = newIntLit(c.graph, n.info, 1)
        boolLit.typ() = getSysType(c.graph, n.info, tyBool)
        result.add boolLit
  of attachedWasMoved:
    result = n
    let t = n[1].typ.skipTypes(abstractVar)
    let op = getAttachedOp(c.graph, t, attachedWasMoved)
    if op != nil:
      result[0] = newSymNode(op)
      analyseIfAddressTakenInCall(c, result, false)
  of attachedSink:
    result = c.semAsgnOpr(c, n, nkSinkAsgn)
  of attachedAsgn:
    result = c.semAsgnOpr(c, n, nkAsgn)
  of attachedDeepCopy:
    result = n
    let t = n[1].typ.skipTypes(abstractVar)
    let op = getAttachedOp(c.graph, t, kind)
    if op != nil:
      result[0] = newSymNode(op)
