disallow recursive module dependencies

This commit is contained in:
Araq 2016-11-23 23:23:31 +01:00
commit 074f276c8a
14 changed files with 47 additions and 45 deletions

View file

@ -162,9 +162,23 @@ proc importModuleAs(n: PNode, realModule: PSym): PSym =
proc myImportModule(c: PContext, n: PNode): PSym = proc myImportModule(c: PContext, n: PNode): PSym =
var f = checkModuleName(n) var f = checkModuleName(n)
if f != InvalidFileIDX: if f != InvalidFileIDX:
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):
if i > 0: err.add "\n"
err.add toFullPath(c.graph.importStack[i]) & " imports " &
toFullPath(c.graph.importStack[i+1])
localError(n.info, "recursive module dependency detected:\n" & err)
result = importModuleAs(n, gImportModule(c.graph, c.module, f, c.cache)) result = importModuleAs(n, gImportModule(c.graph, c.module, f, c.cache))
#echo "set back to ", L
c.graph.importStack.setLen(L)
# we cannot perform this check reliably because of # we cannot perform this check reliably because of
# test: modules/import_in_config) # test: modules/import_in_config)
when false:
if result.info.fileIndex == c.module.info.fileIndex and if result.info.fileIndex == c.module.info.fileIndex and
result.info.fileIndex == n.info.fileIndex: result.info.fileIndex == n.info.fileIndex:
localError(n.info, errGenerated, "A module cannot import itself") localError(n.info, errGenerated, "A module cannot import itself")

View file

@ -36,6 +36,8 @@ type
invalidTransitiveClosure: bool invalidTransitiveClosure: bool
inclToMod*: Table[int32, int32] # mapping of include file to the inclToMod*: Table[int32, int32] # mapping of include file to the
# first module that included it # first module that included it
importStack*: seq[int32] # The current import stack. Used for detecting recursive
# module dependencies.
{.this: g.} {.this: g.}
@ -44,12 +46,14 @@ proc newModuleGraph*(): ModuleGraph =
initStrTable(result.packageSyms) initStrTable(result.packageSyms)
result.deps = initIntSet() result.deps = initIntSet()
result.modules = @[] result.modules = @[]
result.importStack = @[]
result.inclToMod = initTable[int32, int32]() result.inclToMod = initTable[int32, int32]()
proc resetAllModules*(g: ModuleGraph) = proc resetAllModules*(g: ModuleGraph) =
initStrTable(packageSyms) initStrTable(packageSyms)
deps = initIntSet() deps = initIntSet()
modules = @[] modules = @[]
importStack = @[]
inclToMod = initTable[int32, int32]() inclToMod = initTable[int32, int32]()
proc getModule*(g: ModuleGraph; fileIdx: int32): PSym = proc getModule*(g: ModuleGraph; fileIdx: int32): PSym =

View file

@ -231,6 +231,7 @@ proc compileProject*(graph: ModuleGraph; cache: IdentCache;
wantMainModule() wantMainModule()
let systemFileIdx = fileInfoIdx(options.libpath / "system.nim") let systemFileIdx = fileInfoIdx(options.libpath / "system.nim")
let projectFile = if projectFileIdx < 0: gProjectMainIdx else: projectFileIdx let projectFile = if projectFileIdx < 0: gProjectMainIdx else: projectFileIdx
graph.importStack.add projectFile
if projectFile == systemFileIdx: if projectFile == systemFileIdx:
discard graph.compileModule(projectFile, cache, {sfMainModule, sfSystemModule}) discard graph.compileModule(projectFile, cache, {sfMainModule, sfSystemModule})
else: else:

View file

@ -676,9 +676,8 @@ proc getInfoContext*(index: int): TLineInfo =
if i >=% L: result = unknownLineInfo() if i >=% L: result = unknownLineInfo()
else: result = msgContext[i] else: result = msgContext[i]
proc toFilename*(fileIdx: int32): string = template toFilename*(fileIdx: int32): string =
if fileIdx < 0: result = "???" (if fileIdx < 0: "???" else: fileInfos[fileIdx].projPath)
else: result = fileInfos[fileIdx].projPath
proc toFullPath*(fileIdx: int32): string = proc toFullPath*(fileIdx: int32): string =
if fileIdx < 0: result = "???" if fileIdx < 0: result = "???"

View file

@ -46,7 +46,7 @@ proc handleCmdLine(cache: IdentCache) =
if gProjectName == "-": if gProjectName == "-":
gProjectName = "stdinfile" gProjectName = "stdinfile"
gProjectFull = "stdinfile" gProjectFull = "stdinfile"
gProjectPath = getCurrentDir() gProjectPath = canonicalizePath getCurrentDir()
gProjectIsStdin = true gProjectIsStdin = true
elif gProjectName != "": elif gProjectName != "":
try: try:
@ -54,10 +54,10 @@ proc handleCmdLine(cache: IdentCache) =
except OSError: except OSError:
gProjectFull = gProjectName gProjectFull = gProjectName
let p = splitFile(gProjectFull) let p = splitFile(gProjectFull)
gProjectPath = p.dir gProjectPath = canonicalizePath p.dir
gProjectName = p.name gProjectName = p.name
else: else:
gProjectPath = getCurrentDir() gProjectPath = canonicalizePath getCurrentDir()
loadConfigs(DefaultConfig) # load all config files loadConfigs(DefaultConfig) # load all config files
let scriptFile = gProjectFull.changeFileExt("nims") let scriptFile = gProjectFull.changeFileExt("nims")
if fileExists(scriptFile): if fileExists(scriptFile):

View file

@ -9,36 +9,8 @@ subtle. Only top-level symbols that are marked with an asterisk (``*``) are
exported. A valid module name can only be a valid Nim identifier (and thus its exported. A valid module name can only be a valid Nim identifier (and thus its
filename is ``identifier.nim``). filename is ``identifier.nim``).
The algorithm for compiling modules is: Recursive module dependencies are not allowed. This restriction might be mitigated
or removed in later versions of the language.
- compile the whole module as usual, following import statements recursively
- if there is a cycle only import the already parsed symbols (that are
exported); if an unknown identifier occurs then abort
This is best illustrated by an example:
.. code-block:: nim
# Module A
type
T1* = int # Module A exports the type ``T1``
import B # the compiler starts parsing B
proc main() =
var i = p(3) # works because B has been parsed completely here
main()
.. code-block:: nim
# Module B
import A # A is not parsed here! Only the already known symbols
# of A are imported.
proc p*(x: A.T1): A.T1 =
# this works because the compiler has already
# added T1 to A's interface symbol table
result = x + 1
Import statement Import statement

View file

@ -1,7 +1,7 @@
discard """ discard """
file: "tests/reject/trecincb.nim" file: "trecincb.nim"
line: 9 line: 9
errormsg: "recursive dependency: 'tests/modules/trecincb.nim'" errormsg: "recursive dependency: 'trecincb.nim'"
""" """
# Test recursive includes # Test recursive includes

View file

@ -1,7 +1,7 @@
discard """ discard """
file: "trecincb.nim" file: "trecincb.nim"
line: 9 line: 9
errormsg: "recursive dependency: 'tests/modules/trecincb.nim'" errormsg: "recursive dependency: 'trecincb.nim'"
""" """
# Test recursive includes # Test recursive includes

View file

@ -1,2 +1,7 @@
discard """
file: "mrecmod.nim"
line: 1
errormsg: "recursive module dependency detected"
"""
# recursive module # recursive module
import mrecmod import mrecmod

View file

@ -1,3 +1,8 @@
discard """
file: "mrecmod2.nim"
line: 2
errormsg: "recursive module dependency detected"
"""
type type
T1* = int # Module A exports the type ``T1`` T1* = int # Module A exports the type ``T1``

View file

@ -1,7 +1,7 @@
discard """ discard """
file: "tselfimport.nim" file: "tselfimport.nim"
line: 7 line: 7
errormsg: "A module cannot import itself" errormsg: "recursive module dependency detected"
""" """
import strutils as su # guard against regression import strutils as su # guard against regression
import tselfimport #ERROR import tselfimport #ERROR

0
tests/system/tdeepcopy.nim Executable file → Normal file
View file

View file

@ -431,10 +431,10 @@ proc handleCmdLine(cache: IdentCache) =
except OSError: except OSError:
gProjectFull = gProjectName gProjectFull = gProjectName
var p = splitFile(gProjectFull) var p = splitFile(gProjectFull)
gProjectPath = p.dir gProjectPath = canonicalizePath p.dir
gProjectName = p.name gProjectName = p.name
else: else:
gProjectPath = getCurrentDir() gProjectPath = canonicalizePath getCurrentDir()
# Find Nim's prefix dir. # Find Nim's prefix dir.
let binaryPath = findExe("nim") let binaryPath = findExe("nim")

View file

@ -29,6 +29,8 @@ Changes affecting backwards compatibility
- ``TimeInfo.tzname`` has been removed from ``times`` module because it was - ``TimeInfo.tzname`` has been removed from ``times`` module because it was
broken. Because of this, the option ``"ZZZ"`` will no longer work in format broken. Because of this, the option ``"ZZZ"`` will no longer work in format
strings for formatting and parsing. strings for formatting and parsing.
- Recursive module dependencies are now completely disallowed.
Library Additions Library Additions
----------------- -----------------