diff options
Diffstat (limited to 'compiler/importer.nim')
-rw-r--r-- | compiler/importer.nim | 428 |
1 files changed, 309 insertions, 119 deletions
diff --git a/compiler/importer.nim b/compiler/importer.nim index 89ee00b9d..ffb7e0305 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -7,98 +7,214 @@ # distribution, for details about the copyright. # -# This module implements the symbol importing mechanism. +## This module implements the symbol importing mechanism. import - intsets, strutils, os, ast, astalgo, msgs, options, idents, rodread, lookups, - semdata, passes, renderer, modulepaths + ast, astalgo, msgs, options, idents, lookups, + semdata, modulepaths, sigmatch, lineinfos, + modulegraphs, wordrecg +from std/strutils import `%`, startsWith +from std/sequtils import addUnique +import std/[sets, tables, intsets] -proc evalImport*(c: PContext, n: PNode): PNode -proc evalFrom*(c: PContext, n: PNode): PNode +when defined(nimPreviewSlimSystem): + import std/assertions -proc importPureEnumField*(c: PContext; s: PSym) = - var check = strTableGet(c.importTable.symbols, s.name) - if check == nil: +proc readExceptSet*(c: PContext, n: PNode): IntSet = + assert n.kind in {nkImportExceptStmt, nkExportExceptStmt} + result = initIntSet() + for i in 1..<n.len: + let ident = lookups.considerQuotedIdent(c, n[i]) + result.incl(ident.id) + +proc declarePureEnumField*(c: PContext; s: PSym) = + # XXX Remove the outer 'if' statement and see what breaks. + var amb = false + if someSymFromImportTable(c, s.name, amb) == nil: strTableAdd(c.pureEnumFields, s) + when false: + let checkB = strTableGet(c.pureEnumFields, s.name) + if checkB == nil: + strTableAdd(c.pureEnumFields, s) + when false: + # mark as ambiguous: + incl(c.ambiguousSymbols, checkB.id) + incl(c.ambiguousSymbols, s.id) -proc rawImportSymbol(c: PContext, s: PSym) = +proc importPureEnumField(c: PContext; s: PSym) = + var amb = false + if someSymFromImportTable(c, s.name, amb) == nil: + strTableAdd(c.pureEnumFields, s) + when false: + let checkB = strTableGet(c.pureEnumFields, s.name) + if checkB == nil: + strTableAdd(c.pureEnumFields, s) + when false: + # mark as ambiguous: + incl(c.ambiguousSymbols, checkB.id) + incl(c.ambiguousSymbols, s.id) + +proc importPureEnumFields(c: PContext; s: PSym; etyp: PType) = + assert sfPure in s.flags + for j in 0..<etyp.n.len: + var e = etyp.n[j].sym + if e.kind != skEnumField: + internalError(c.config, s.info, "rawImportSymbol") + # BUGFIX: because of aliases for enums the symbol may already + # have been put into the symbol table + # BUGFIX: but only iff they are the same symbols! + for check in importedItems(c, e.name): + if check.id == e.id: + e = nil + break + if e != nil: + importPureEnumField(c, e) + +proc rawImportSymbol(c: PContext, s, origin: PSym; importSet: var IntSet) = # This does not handle stubs, because otherwise loading on demand would be # pointless in practice. So importing stubs is fine here! # check if we have already a symbol of the same name: - var check = strTableGet(c.importTable.symbols, s.name) - if check != nil and check.id != s.id: - if s.kind notin OverloadableSyms: - # s and check need to be qualified: - incl(c.ambiguousSymbols, s.id) - incl(c.ambiguousSymbols, check.id) + when false: + var check = someSymFromImportTable(c, s.name) + if check != nil and check.id != s.id: + if s.kind notin OverloadableSyms or check.kind notin OverloadableSyms: + # s and check need to be qualified: + incl(c.ambiguousSymbols, s.id) + incl(c.ambiguousSymbols, check.id) # thanks to 'export' feature, it could be we import the same symbol from - # multiple sources, so we need to call 'StrTableAdd' here: - strTableAdd(c.importTable.symbols, s) + # multiple sources, so we need to call 'strTableAdd' here: + when false: + # now lazy. Speeds up the compiler and is a prerequisite for IC. + strTableAdd(c.importTable.symbols, s) + else: + importSet.incl s.id if s.kind == skType: var etyp = s.typ if etyp.kind in {tyBool, tyEnum}: - for j in countup(0, sonsLen(etyp.n) - 1): - var e = etyp.n.sons[j].sym + for j in 0..<etyp.n.len: + var e = etyp.n[j].sym if e.kind != skEnumField: - internalError(s.info, "rawImportSymbol") + internalError(c.config, s.info, "rawImportSymbol") # BUGFIX: because of aliases for enums the symbol may already # have been put into the symbol table # BUGFIX: but only iff they are the same symbols! - var it: TIdentIter - check = initIdentIter(it, c.importTable.symbols, e.name) - while check != nil: + for check in importedItems(c, e.name): if check.id == e.id: e = nil break - check = nextIdentIter(it, c.importTable.symbols) if e != nil: if sfPure notin s.flags: - rawImportSymbol(c, e) + rawImportSymbol(c, e, origin, importSet) else: importPureEnumField(c, e) else: - # rodgen assures that converters and patterns are no stubs - if s.kind == skConverter: addConverter(c, s) - if hasPattern(s): addPattern(c, s) + if s.kind == skConverter: addConverter(c, LazySym(sym: s)) + if hasPattern(s): addPattern(c, LazySym(sym: s)) + if s.owner != origin: + c.exportIndirections.incl((origin.id, s.id)) + +proc splitPragmas(c: PContext, n: PNode): (PNode, seq[TSpecialWord]) = + template bail = globalError(c.config, n.info, "invalid pragma") + result = (nil, @[]) + if n.kind == nkPragmaExpr: + if n.len == 2 and n[1].kind == nkPragma: + result[0] = n[0] + for ni in n[1]: + if ni.kind == nkIdent: result[1].add whichKeyword(ni.ident) + else: bail() + else: bail() + else: + result[0] = n + if result[0].safeLen > 0: + (result[0][^1], result[1]) = splitPragmas(c, result[0][^1]) -proc importSymbol(c: PContext, n: PNode, fromMod: PSym) = - let ident = lookups.considerQuotedIdent(n) - let s = strTableGet(fromMod.tab, ident) +proc importSymbol(c: PContext, n: PNode, fromMod: PSym; importSet: var IntSet) = + let (n, kws) = splitPragmas(c, n) + if kws.len > 0: + globalError(c.config, n.info, "unexpected pragma") + + let ident = lookups.considerQuotedIdent(c, n) + let s = someSym(c.graph, fromMod, ident) if s == nil: errorUndeclaredIdentifier(c, n.info, ident.s) else: - if s.kind == skStub: loadStub(s) - if s.kind notin ExportableSymKinds: - internalError(n.info, "importSymbol: 2") + when false: + if s.kind == skStub: loadStub(s) + let multiImport = s.kind notin ExportableSymKinds or s.kind in skProcKinds # for an enumeration we have to add all identifiers - case s.kind - of skProcKinds: + if multiImport: # for a overloadable syms add all overloaded routines - var it: TIdentIter - var e = initIdentIter(it, fromMod.tab, s.name) + var it: ModuleIter = default(ModuleIter) + var e = initModuleIter(it, c.graph, fromMod, s.name) while e != nil: - if e.name.id != s.name.id: internalError(n.info, "importSymbol: 3") - rawImportSymbol(c, e) - e = nextIdentIter(it, fromMod.tab) - else: rawImportSymbol(c, s) + if e.name.id != s.name.id: internalError(c.config, n.info, "importSymbol: 3") + if s.kind in ExportableSymKinds: + rawImportSymbol(c, e, fromMod, importSet) + e = nextModuleIter(it, c.graph) + else: + rawImportSymbol(c, s, fromMod, importSet) + suggestSym(c.graph, n.info, s, c.graph.usageSym, false) + +proc addImport(c: PContext; im: sink ImportedModule) = + for i in 0..high(c.imports): + if c.imports[i].m == im.m: + # we have already imported the module: Check which import + # is more "powerful": + case c.imports[i].mode + of importAll: discard "already imported all symbols" + of importSet: + case im.mode + of importAll, importExcept: + # XXX: slightly wrong semantics for 'importExcept'... + # But we should probably change the spec and disallow this case. + c.imports[i] = im + of importSet: + # merge the import sets: + c.imports[i].imported.incl im.imported + of importExcept: + case im.mode + of importAll: + c.imports[i] = im + of importSet: + discard + of importExcept: + var cut = initIntSet() + # only exclude what is consistent between the two sets: + for j in im.exceptSet: + if j in c.imports[i].exceptSet: + cut.incl j + c.imports[i].exceptSet = cut + return + c.imports.add im + +template addUnnamedIt(c: PContext, fromMod: PSym; filter: untyped) {.dirty.} = + for it in mitems c.graph.ifaces[fromMod.position].converters: + if filter: + loadPackedSym(c.graph, it) + if sfExported in it.sym.flags: + addConverter(c, it) + for it in mitems c.graph.ifaces[fromMod.position].patterns: + if filter: + loadPackedSym(c.graph, it) + if sfExported in it.sym.flags: + addPattern(c, it) + for it in mitems c.graph.ifaces[fromMod.position].pureEnums: + if filter: + loadPackedSym(c.graph, it) + importPureEnumFields(c, it.sym, it.sym.typ) proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: IntSet) = - var i: TTabIter - var s = initTabIter(i, fromMod.tab) - while s != nil: - if s.kind != skModule: - if s.kind != skEnumField: - if s.kind notin ExportableSymKinds: - internalError(s.info, "importAllSymbols: " & $s.kind) - if exceptSet.isNil or s.name.id notin exceptSet: - rawImportSymbol(c, s) - s = nextIter(i, fromMod.tab) + c.addImport ImportedModule(m: fromMod, mode: importExcept, exceptSet: exceptSet) + addUnnamedIt(c, fromMod, it.sym.name.id notin exceptSet) proc importAllSymbols*(c: PContext, fromMod: PSym) = - var exceptSet: IntSet - importAllSymbolsExcept(c, fromMod, exceptSet) + c.addImport ImportedModule(m: fromMod, mode: importAll) + addUnnamedIt(c, fromMod, true) + when false: + var exceptSet: IntSet + importAllSymbolsExcept(c, fromMod, exceptSet) -proc importForwarded(c: PContext, n: PNode, exceptSet: IntSet) = +proc importForwarded(c: PContext, n: PNode, exceptSet: IntSet; fromMod: PSym; importSet: var IntSet) = if n.isNil: return case n.kind of nkExportStmt: @@ -108,96 +224,170 @@ proc importForwarded(c: PContext, n: PNode, exceptSet: IntSet) = if s.kind == skModule: importAllSymbolsExcept(c, s, exceptSet) elif exceptSet.isNil or s.name.id notin exceptSet: - rawImportSymbol(c, s) + rawImportSymbol(c, s, fromMod, importSet) of nkExportExceptStmt: - localError(n.info, errGenerated, "'export except' not implemented") + localError(c.config, n.info, "'export except' not implemented") else: - for i in 0..safeLen(n)-1: - importForwarded(c, n.sons[i], exceptSet) + for i in 0..n.safeLen-1: + importForwarded(c, n[i], exceptSet, fromMod, importSet) -proc importModuleAs(n: PNode, realModule: PSym): PSym = +proc importModuleAs(c: PContext; n: PNode, realModule: PSym, importHidden: bool): PSym = result = realModule + template createModuleAliasImpl(ident): untyped = + createModuleAlias(realModule, c.idgen, ident, n.info, c.config.options) if n.kind != nkImportAs: discard - elif n.len != 2 or n.sons[1].kind != nkIdent: - localError(n.info, errGenerated, "module alias must be an identifier") - elif n.sons[1].ident.id != realModule.name.id: + elif n.len != 2 or n[1].kind != nkIdent: + localError(c.config, n.info, "module alias must be an identifier") + elif n[1].ident.id != realModule.name.id: # some misguided guy will write 'import abc.foo as foo' ... - result = createModuleAlias(realModule, n.sons[1].ident, realModule.info) + result = createModuleAliasImpl(n[1].ident) + if result == realModule: + # avoids modifying `realModule`, see D20201209T194412 for `import {.all.}` + result = createModuleAliasImpl(realModule.name) + if importHidden: + result.options.incl optImportHidden + let moduleIdent = if n.kind == nkInfix: n[^1] else: n + c.unusedImports.add((result, moduleIdent.info)) + c.importModuleMap[result.id] = realModule.id + c.importModuleLookup.mgetOrPut(result.name.id, @[]).addUnique realModule.id + +proc transformImportAs(c: PContext; n: PNode): tuple[node: PNode, importHidden: bool] = + result = (nil, false) + var ret = default(typeof(result)) + proc processPragma(n2: PNode): PNode = + let (result2, kws) = splitPragmas(c, n2) + result = result2 + for ai in kws: + case ai + of wImportHidden: ret.importHidden = true + else: globalError(c.config, n.info, "invalid pragma, expected: " & ${wImportHidden}) + + if n.kind == nkInfix and considerQuotedIdent(c, n[0]).s == "as": + ret.node = newNodeI(nkImportAs, n.info) + ret.node.add n[1].processPragma + ret.node.add n[2] + else: + ret.node = n.processPragma + return ret -proc myImportModule(c: PContext, n: PNode): PSym = - var f = checkModuleName(n) - if f != InvalidFileIDX: +proc myImportModule(c: PContext, n: var PNode, importStmtResult: PNode): PSym = + let transf = transformImportAs(c, n) + n = transf.node + let f = checkModuleName(c.config, n) + if f != InvalidFileIdx: + addImportFileDep(c, f) let L = c.graph.importStack.len let recursion = c.graph.importStack.find(f) c.graph.importStack.add f #echo "adding ", toFullPath(f), " at ", L+1 if recursion >= 0: var err = "" - for i in countup(recursion, L-1): + for i in recursion..<L: if i > recursion: err.add "\n" - err.add toFullPath(c.graph.importStack[i]) & " imports " & - toFullPath(c.graph.importStack[i+1]) + err.add toFullPath(c.config, c.graph.importStack[i]) & " imports " & + toFullPath(c.config, c.graph.importStack[i+1]) c.recursiveDep = err - result = importModuleAs(n, gImportModule(c.graph, c.module, f, c.cache)) + + var realModule: PSym + discard pushOptionEntry(c) + realModule = c.graph.importModuleCallback(c.graph, c.module, f) + result = importModuleAs(c, n, realModule, transf.importHidden) + popOptionEntry(c) + #echo "set back to ", L c.graph.importStack.setLen(L) # we cannot perform this check reliably because of - # test: modules/import_in_config) - when true: - if result.info.fileIndex == c.module.info.fileIndex and - result.info.fileIndex == n.info.fileIndex: - localError(n.info, errGenerated, "A module cannot import itself") - if sfDeprecated in result.flags: - message(n.info, warnDeprecated, result.name.s) - #suggestSym(n.info, result, false) - -proc impMod(c: PContext; it: PNode) = - let m = myImportModule(c, it) + # test: modules/import_in_config) # xxx is that still true? + if realModule == c.module: + localError(c.config, n.info, "module '$1' cannot import itself" % realModule.name.s) + if sfDeprecated in realModule.flags: + var prefix = "" + if realModule.constraint != nil: prefix = realModule.constraint.strVal & "; " + message(c.config, n.info, warnDeprecated, prefix & realModule.name.s & " is deprecated") + let moduleName = getModuleName(c.config, n) + if belongsToStdlib(c.graph, result) and not startsWith(moduleName, stdPrefix) and + not startsWith(moduleName, "system/") and not startsWith(moduleName, "packages/"): + message(c.config, n.info, warnStdPrefix, realModule.name.s) + + proc suggestMod(n: PNode; s: PSym) = + if n.kind == nkImportAs: + suggestMod(n[0], realModule) + elif n.kind == nkInfix: + suggestMod(n[2], s) + else: + suggestSym(c.graph, n.info, s, c.graph.usageSym, false) + suggestMod(n, result) + importStmtResult.add newSymNode(result, n.info) + #newStrNode(toFullPath(c.config, f), n.info) + else: + result = nil + +proc afterImport(c: PContext, m: PSym) = + if isCachedModule(c.graph, m): return + # fixes bug #17510, for re-exported symbols + let realModuleId = c.importModuleMap[m.id] + for s in allSyms(c.graph, m): + if s.owner.id != realModuleId: + c.exportIndirections.incl((m.id, s.id)) + +proc impMod(c: PContext; it: PNode; importStmtResult: PNode) = + var it = it + let m = myImportModule(c, it, importStmtResult) if m != nil: - var emptySet: IntSet # ``addDecl`` needs to be done before ``importAllSymbols``! addDecl(c, m, it.info) # add symbol to symbol table of module - importAllSymbolsExcept(c, m, emptySet) - #importForwarded(c, m.ast, emptySet) - -proc evalImport(c: PContext, n: PNode): PNode = - result = n - for i in countup(0, sonsLen(n) - 1): - let it = n.sons[i] - if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket: - let sep = it[0] - let dir = it[1] - let a = newNodeI(nkInfix, it.info) - a.add sep - a.add dir - a.add sep # dummy entry, replaced in the loop - for x in it[2]: - a.sons[2] = x - impMod(c, a) + importAllSymbols(c, m) + #importForwarded(c, m.ast, emptySet, m) + afterImport(c, m) + +proc evalImport*(c: PContext, n: PNode): PNode = + result = newNodeI(nkImportStmt, n.info) + for i in 0..<n.len: + let it = n[i] + 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]: + # transform `a/b/[c as d]` to `/a/b/c as d` + if x.kind == nkInfix and x[0].ident.s == "as": + var impAs = copyNode(x) + newSons(impAs, 3) + impAs[0] = x[0] + imp[lastPos] = x[1] + impAs[1] = imp + impAs[2] = x[2] + impMod(c, impAs, result) + else: + imp[lastPos] = x + impMod(c, imp, result) else: - impMod(c, it) + impMod(c, it, result) -proc evalFrom(c: PContext, n: PNode): PNode = - result = n - checkMinSonsLen(n, 2) - var m = myImportModule(c, n.sons[0]) +proc evalFrom*(c: PContext, n: PNode): PNode = + result = newNodeI(nkImportStmt, n.info) + checkMinSonsLen(n, 2, c.config) + var m = myImportModule(c, n[0], result) if m != nil: - n.sons[0] = newSymNode(m) + n[0] = newSymNode(m) addDecl(c, m, n.info) # add symbol to symbol table of module - for i in countup(1, sonsLen(n) - 1): - if n.sons[i].kind != nkNilLit: - importSymbol(c, n.sons[i], m) + + var im = ImportedModule(m: m, mode: importSet, imported: initIntSet()) + for i in 1..<n.len: + if n[i].kind != nkNilLit: + importSymbol(c, n[i], m, im.imported) + c.addImport im + afterImport(c, m) proc evalImportExcept*(c: PContext, n: PNode): PNode = - result = n - checkMinSonsLen(n, 2) - var m = myImportModule(c, n.sons[0]) + result = newNodeI(nkImportStmt, n.info) + checkMinSonsLen(n, 2, c.config) + var m = myImportModule(c, n[0], result) if m != nil: - n.sons[0] = newSymNode(m) + n[0] = newSymNode(m) addDecl(c, m, n.info) # add symbol to symbol table of module - var exceptSet = initIntSet() - for i in countup(1, sonsLen(n) - 1): - let ident = lookups.considerQuotedIdent(n.sons[i]) - exceptSet.incl(ident.id) - importAllSymbolsExcept(c, m, exceptSet) - #importForwarded(c, m.ast, exceptSet) + importAllSymbolsExcept(c, m, readExceptSet(c, n)) + #importForwarded(c, m.ast, exceptSet, m) + afterImport(c, m) |