1#
2#
3#           The Nim Compiler
4#        (c) Copyright 2013 Andreas Rumpf
5#
6#    See the file "copying.txt", included in this
7#    distribution, for details about the copyright.
8#
9
10## This module implements the symbol importing mechanism.
11
12import
13  intsets, ast, astalgo, msgs, options, idents, lookups,
14  semdata, modulepaths, sigmatch, lineinfos, sets,
15  modulegraphs, wordrecg, tables
16from strutils import `%`
17
18proc readExceptSet*(c: PContext, n: PNode): IntSet =
19  assert n.kind in {nkImportExceptStmt, nkExportExceptStmt}
20  result = initIntSet()
21  for i in 1..<n.len:
22    let ident = lookups.considerQuotedIdent(c, n[i])
23    result.incl(ident.id)
24
25proc declarePureEnumField*(c: PContext; s: PSym) =
26  # XXX Remove the outer 'if' statement and see what breaks.
27  var amb = false
28  if someSymFromImportTable(c, s.name, amb) == nil:
29    strTableAdd(c.pureEnumFields, s)
30    when false:
31      let checkB = strTableGet(c.pureEnumFields, s.name)
32      if checkB == nil:
33        strTableAdd(c.pureEnumFields, s)
34    when false:
35      # mark as ambiguous:
36      incl(c.ambiguousSymbols, checkB.id)
37      incl(c.ambiguousSymbols, s.id)
38
39proc importPureEnumField(c: PContext; s: PSym) =
40  var amb = false
41  if someSymFromImportTable(c, s.name, amb) == nil:
42    strTableAdd(c.pureEnumFields, s)
43    when false:
44      let checkB = strTableGet(c.pureEnumFields, s.name)
45      if checkB == nil:
46        strTableAdd(c.pureEnumFields, s)
47    when false:
48      # mark as ambiguous:
49      incl(c.ambiguousSymbols, checkB.id)
50      incl(c.ambiguousSymbols, s.id)
51
52proc importPureEnumFields(c: PContext; s: PSym; etyp: PType) =
53  assert sfPure in s.flags
54  for j in 0..<etyp.n.len:
55    var e = etyp.n[j].sym
56    if e.kind != skEnumField:
57      internalError(c.config, s.info, "rawImportSymbol")
58      # BUGFIX: because of aliases for enums the symbol may already
59      # have been put into the symbol table
60      # BUGFIX: but only iff they are the same symbols!
61    for check in importedItems(c, e.name):
62      if check.id == e.id:
63        e = nil
64        break
65    if e != nil:
66      importPureEnumField(c, e)
67
68proc rawImportSymbol(c: PContext, s, origin: PSym; importSet: var IntSet) =
69  # This does not handle stubs, because otherwise loading on demand would be
70  # pointless in practice. So importing stubs is fine here!
71  # check if we have already a symbol of the same name:
72  when false:
73    var check = someSymFromImportTable(c, s.name)
74    if check != nil and check.id != s.id:
75      if s.kind notin OverloadableSyms or check.kind notin OverloadableSyms:
76        # s and check need to be qualified:
77        incl(c.ambiguousSymbols, s.id)
78        incl(c.ambiguousSymbols, check.id)
79  # thanks to 'export' feature, it could be we import the same symbol from
80  # multiple sources, so we need to call 'strTableAdd' here:
81  when false:
82    # now lazy. Speeds up the compiler and is a prerequisite for IC.
83    strTableAdd(c.importTable.symbols, s)
84  else:
85    importSet.incl s.id
86  if s.kind == skType:
87    var etyp = s.typ
88    if etyp.kind in {tyBool, tyEnum}:
89      for j in 0..<etyp.n.len:
90        var e = etyp.n[j].sym
91        if e.kind != skEnumField:
92          internalError(c.config, s.info, "rawImportSymbol")
93          # BUGFIX: because of aliases for enums the symbol may already
94          # have been put into the symbol table
95          # BUGFIX: but only iff they are the same symbols!
96        for check in importedItems(c, e.name):
97          if check.id == e.id:
98            e = nil
99            break
100        if e != nil:
101          if sfPure notin s.flags:
102            rawImportSymbol(c, e, origin, importSet)
103          else:
104            importPureEnumField(c, e)
105  else:
106    if s.kind == skConverter: addConverter(c, LazySym(sym: s))
107    if hasPattern(s): addPattern(c, LazySym(sym: s))
108  if s.owner != origin:
109    c.exportIndirections.incl((origin.id, s.id))
110
111proc splitPragmas(c: PContext, n: PNode): (PNode, seq[TSpecialWord]) =
112  template bail = globalError(c.config, n.info, "invalid pragma")
113  if n.kind == nkPragmaExpr:
114    if n.len == 2 and n[1].kind == nkPragma:
115      result[0] = n[0]
116      for ni in n[1]:
117        if ni.kind == nkIdent: result[1].add whichKeyword(ni.ident)
118        else: bail()
119    else: bail()
120  else:
121    result[0] = n
122    if result[0].safeLen > 0:
123      (result[0][^1], result[1]) = splitPragmas(c, result[0][^1])
124
125proc importSymbol(c: PContext, n: PNode, fromMod: PSym; importSet: var IntSet) =
126  let (n, kws) = splitPragmas(c, n)
127  if kws.len > 0:
128    globalError(c.config, n.info, "unexpected pragma")
129
130  let ident = lookups.considerQuotedIdent(c, n)
131  let s = someSym(c.graph, fromMod, ident)
132  if s == nil:
133    errorUndeclaredIdentifier(c, n.info, ident.s)
134  else:
135    when false:
136      if s.kind == skStub: loadStub(s)
137    let multiImport = s.kind notin ExportableSymKinds or s.kind in skProcKinds
138    # for an enumeration we have to add all identifiers
139    if multiImport:
140      # for a overloadable syms add all overloaded routines
141      var it: ModuleIter
142      var e = initModuleIter(it, c.graph, fromMod, s.name)
143      while e != nil:
144        if e.name.id != s.name.id: internalError(c.config, n.info, "importSymbol: 3")
145        if s.kind in ExportableSymKinds:
146          rawImportSymbol(c, e, fromMod, importSet)
147        e = nextModuleIter(it, c.graph)
148    else:
149      rawImportSymbol(c, s, fromMod, importSet)
150    suggestSym(c.graph, n.info, s, c.graph.usageSym, false)
151
152proc addImport(c: PContext; im: sink ImportedModule) =
153  for i in 0..high(c.imports):
154    if c.imports[i].m == im.m:
155      # we have already imported the module: Check which import
156      # is more "powerful":
157      case c.imports[i].mode
158      of importAll: discard "already imported all symbols"
159      of importSet:
160        case im.mode
161        of importAll, importExcept:
162          # XXX: slightly wrong semantics for 'importExcept'...
163          # But we should probably change the spec and disallow this case.
164          c.imports[i] = im
165        of importSet:
166          # merge the import sets:
167          c.imports[i].imported.incl im.imported
168      of importExcept:
169        case im.mode
170        of importAll:
171          c.imports[i] = im
172        of importSet:
173          discard
174        of importExcept:
175          var cut = initIntSet()
176          # only exclude what is consistent between the two sets:
177          for j in im.exceptSet:
178            if j in c.imports[i].exceptSet:
179              cut.incl j
180          c.imports[i].exceptSet = cut
181      return
182  c.imports.add im
183
184template addUnnamedIt(c: PContext, fromMod: PSym; filter: untyped) {.dirty.} =
185  for it in mitems c.graph.ifaces[fromMod.position].converters:
186    if filter:
187      loadPackedSym(c.graph, it)
188      if sfExported in it.sym.flags:
189        addConverter(c, it)
190  for it in mitems c.graph.ifaces[fromMod.position].patterns:
191    if filter:
192      loadPackedSym(c.graph, it)
193      if sfExported in it.sym.flags:
194        addPattern(c, it)
195  for it in mitems c.graph.ifaces[fromMod.position].pureEnums:
196    if filter:
197      loadPackedSym(c.graph, it)
198      importPureEnumFields(c, it.sym, it.sym.typ)
199
200proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: IntSet) =
201  c.addImport ImportedModule(m: fromMod, mode: importExcept, exceptSet: exceptSet)
202  addUnnamedIt(c, fromMod, it.sym.id notin exceptSet)
203
204proc importAllSymbols*(c: PContext, fromMod: PSym) =
205  c.addImport ImportedModule(m: fromMod, mode: importAll)
206  addUnnamedIt(c, fromMod, true)
207  when false:
208    var exceptSet: IntSet
209    importAllSymbolsExcept(c, fromMod, exceptSet)
210
211proc importForwarded(c: PContext, n: PNode, exceptSet: IntSet; fromMod: PSym; importSet: var IntSet) =
212  if n.isNil: return
213  case n.kind
214  of nkExportStmt:
215    for a in n:
216      assert a.kind == nkSym
217      let s = a.sym
218      if s.kind == skModule:
219        importAllSymbolsExcept(c, s, exceptSet)
220      elif exceptSet.isNil or s.name.id notin exceptSet:
221        rawImportSymbol(c, s, fromMod, importSet)
222  of nkExportExceptStmt:
223    localError(c.config, n.info, "'export except' not implemented")
224  else:
225    for i in 0..n.safeLen-1:
226      importForwarded(c, n[i], exceptSet, fromMod, importSet)
227
228proc importModuleAs(c: PContext; n: PNode, realModule: PSym, importHidden: bool): PSym =
229  result = realModule
230  template createModuleAliasImpl(ident): untyped =
231    createModuleAlias(realModule, nextSymId c.idgen, ident, n.info, c.config.options)
232  if n.kind != nkImportAs: discard
233  elif n.len != 2 or n[1].kind != nkIdent:
234    localError(c.config, n.info, "module alias must be an identifier")
235  elif n[1].ident.id != realModule.name.id:
236    # some misguided guy will write 'import abc.foo as foo' ...
237    result = createModuleAliasImpl(n[1].ident)
238  if result == realModule:
239    # avoids modifying `realModule`, see D20201209T194412 for `import {.all.}`
240    result = createModuleAliasImpl(realModule.name)
241  if importHidden:
242    result.options.incl optImportHidden
243  c.unusedImports.add((result, n.info))
244  c.importModuleMap[result.id] = realModule.id
245
246proc transformImportAs(c: PContext; n: PNode): tuple[node: PNode, importHidden: bool] =
247  var ret: typeof(result)
248  proc processPragma(n2: PNode): PNode =
249    let (result2, kws) = splitPragmas(c, n2)
250    result = result2
251    for ai in kws:
252      case ai
253      of wImportHidden: ret.importHidden = true
254      else: globalError(c.config, n.info, "invalid pragma, expected: " & ${wImportHidden})
255
256  if n.kind == nkInfix and considerQuotedIdent(c, n[0]).s == "as":
257    ret.node = newNodeI(nkImportAs, n.info)
258    ret.node.add n[1].processPragma
259    ret.node.add n[2]
260  else:
261    ret.node = n.processPragma
262  return ret
263
264proc myImportModule(c: PContext, n: var PNode, importStmtResult: PNode): PSym =
265  let transf = transformImportAs(c, n)
266  n = transf.node
267  let f = checkModuleName(c.config, n)
268  if f != InvalidFileIdx:
269    addImportFileDep(c, f)
270    let L = c.graph.importStack.len
271    let recursion = c.graph.importStack.find(f)
272    c.graph.importStack.add f
273    #echo "adding ", toFullPath(f), " at ", L+1
274    if recursion >= 0:
275      var err = ""
276      for i in recursion..<L:
277        if i > recursion: err.add "\n"
278        err.add toFullPath(c.config, c.graph.importStack[i]) & " imports " &
279                toFullPath(c.config, c.graph.importStack[i+1])
280      c.recursiveDep = err
281
282    var realModule: PSym
283    discard pushOptionEntry(c)
284    realModule = c.graph.importModuleCallback(c.graph, c.module, f)
285    result = importModuleAs(c, n, realModule, transf.importHidden)
286    popOptionEntry(c)
287
288    #echo "set back to ", L
289    c.graph.importStack.setLen(L)
290    # we cannot perform this check reliably because of
291    # test: modules/import_in_config) # xxx is that still true?
292    if realModule == c.module:
293      localError(c.config, n.info, "module '$1' cannot import itself" % realModule.name.s)
294    if sfDeprecated in realModule.flags:
295      var prefix = ""
296      if realModule.constraint != nil: prefix = realModule.constraint.strVal & "; "
297      message(c.config, n.info, warnDeprecated, prefix & realModule.name.s & " is deprecated")
298    suggestSym(c.graph, n.info, result, c.graph.usageSym, false)
299    importStmtResult.add newSymNode(result, n.info)
300    #newStrNode(toFullPath(c.config, f), n.info)
301
302proc afterImport(c: PContext, m: PSym) =
303  # fixes bug #17510, for re-exported symbols
304  let realModuleId = c.importModuleMap[m.id]
305  for s in allSyms(c.graph, m):
306    if s.owner.id != realModuleId:
307      c.exportIndirections.incl((m.id, s.id))
308
309proc impMod(c: PContext; it: PNode; importStmtResult: PNode) =
310  var it = it
311  let m = myImportModule(c, it, importStmtResult)
312  if m != nil:
313    # ``addDecl`` needs to be done before ``importAllSymbols``!
314    addDecl(c, m, it.info) # add symbol to symbol table of module
315    importAllSymbols(c, m)
316    #importForwarded(c, m.ast, emptySet, m)
317    afterImport(c, m)
318
319proc evalImport*(c: PContext, n: PNode): PNode =
320  result = newNodeI(nkImportStmt, n.info)
321  for i in 0..<n.len:
322    let it = n[i]
323    if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket:
324      let sep = it[0]
325      let dir = it[1]
326      var imp = newNodeI(nkInfix, it.info)
327      imp.add sep
328      imp.add dir
329      imp.add sep # dummy entry, replaced in the loop
330      for x in it[2]:
331        # transform `a/b/[c as d]` to `/a/b/c as d`
332        if x.kind == nkInfix and x[0].ident.s == "as":
333          let impAs = copyTree(x)
334          imp[2] = x[1]
335          impAs[1] = imp
336          impMod(c, imp, result)
337        else:
338          imp[2] = x
339          impMod(c, imp, result)
340    else:
341      impMod(c, it, result)
342
343proc evalFrom*(c: PContext, n: PNode): PNode =
344  result = newNodeI(nkImportStmt, n.info)
345  checkMinSonsLen(n, 2, c.config)
346  var m = myImportModule(c, n[0], result)
347  if m != nil:
348    n[0] = newSymNode(m)
349    addDecl(c, m, n.info)               # add symbol to symbol table of module
350
351    var im = ImportedModule(m: m, mode: importSet, imported: initIntSet())
352    for i in 1..<n.len:
353      if n[i].kind != nkNilLit:
354        importSymbol(c, n[i], m, im.imported)
355    c.addImport im
356    afterImport(c, m)
357
358proc evalImportExcept*(c: PContext, n: PNode): PNode =
359  result = newNodeI(nkImportStmt, n.info)
360  checkMinSonsLen(n, 2, c.config)
361  var m = myImportModule(c, n[0], result)
362  if m != nil:
363    n[0] = newSymNode(m)
364    addDecl(c, m, n.info)               # add symbol to symbol table of module
365    importAllSymbolsExcept(c, m, readExceptSet(c, n))
366    #importForwarded(c, m.ast, exceptSet, m)
367    afterImport(c, m)
368