recursive modules are only detected to improve error messages

This commit is contained in:
Andreas Rumpf 2016-11-24 12:27:21 +01:00
commit 01ae0d28d4
12 changed files with 57 additions and 23 deletions

View file

@ -100,7 +100,7 @@ proc importSymbol(c: PContext, n: PNode, fromMod: PSym) =
let ident = lookups.considerQuotedIdent(n) let ident = lookups.considerQuotedIdent(n)
let s = strTableGet(fromMod.tab, ident) let s = strTableGet(fromMod.tab, ident)
if s == nil: if s == nil:
localError(n.info, errUndeclaredIdentifier, ident.s) errorUndeclaredIdentifier(c, n.info, ident.s)
else: else:
if s.kind == skStub: loadStub(s) if s.kind == skStub: loadStub(s)
if s.kind notin ExportableSymKinds: if s.kind notin ExportableSymKinds:
@ -172,13 +172,13 @@ proc myImportModule(c: PContext, n: PNode): PSym =
if i > recursion: err.add "\n" if i > recursion: err.add "\n"
err.add toFullPath(c.graph.importStack[i]) & " imports " & err.add toFullPath(c.graph.importStack[i]) & " imports " &
toFullPath(c.graph.importStack[i+1]) toFullPath(c.graph.importStack[i+1])
localError(n.info, "recursive module dependency detected:\n" & err) c.recursiveDep = 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 #echo "set back to ", L
c.graph.importStack.setLen(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: when true:
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

@ -242,6 +242,15 @@ proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) =
inc i inc i
localError(info, errGenerated, err) localError(info, errGenerated, err)
proc errorUndeclaredIdentifier*(c: PContext; info: TLineInfo; name: string) =
var err = "undeclared identifier: '" & name & "'"
if c.recursiveDep.len > 0:
err.add "\nThis might be caused by a recursive module dependency: "
err.add c.recursiveDep
# prevent excessive errors for 'nim check'
c.recursiveDep = nil
localError(info, errGenerated, err)
proc lookUp*(c: PContext, n: PNode): PSym = proc lookUp*(c: PContext, n: PNode): PSym =
# Looks up a symbol. Generates an error in case of nil. # Looks up a symbol. Generates an error in case of nil.
case n.kind case n.kind
@ -249,7 +258,7 @@ proc lookUp*(c: PContext, n: PNode): PSym =
result = searchInScopes(c, n.ident).skipAlias(n) result = searchInScopes(c, n.ident).skipAlias(n)
if result == nil: if result == nil:
fixSpelling(n, n.ident, searchInScopes) fixSpelling(n, n.ident, searchInScopes)
localError(n.info, errUndeclaredIdentifier, n.ident.s) errorUndeclaredIdentifier(c, n.info, n.ident.s)
result = errorSym(c, n) result = errorSym(c, n)
of nkSym: of nkSym:
result = n.sym result = n.sym
@ -258,7 +267,7 @@ proc lookUp*(c: PContext, n: PNode): PSym =
result = searchInScopes(c, ident).skipAlias(n) result = searchInScopes(c, ident).skipAlias(n)
if result == nil: if result == nil:
fixSpelling(n, ident, searchInScopes) fixSpelling(n, ident, searchInScopes)
localError(n.info, errUndeclaredIdentifier, ident.s) errorUndeclaredIdentifier(c, n.info, ident.s)
result = errorSym(c, n) result = errorSym(c, n)
else: else:
internalError(n.info, "lookUp") internalError(n.info, "lookUp")
@ -282,7 +291,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
result = searchInScopes(c, ident, allExceptModule).skipAlias(n) result = searchInScopes(c, ident, allExceptModule).skipAlias(n)
if result == nil and checkUndeclared in flags: if result == nil and checkUndeclared in flags:
fixSpelling(n, ident, searchInScopes) fixSpelling(n, ident, searchInScopes)
localError(n.info, errUndeclaredIdentifier, ident.s) errorUndeclaredIdentifier(c, n.info, ident.s)
result = errorSym(c, n) result = errorSym(c, n)
elif checkAmbiguity in flags and result != nil and elif checkAmbiguity in flags and result != nil and
contains(c.ambiguousSymbols, result.id): contains(c.ambiguousSymbols, result.id):
@ -307,7 +316,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
result = strTableGet(m.tab, ident).skipAlias(n) result = strTableGet(m.tab, ident).skipAlias(n)
if result == nil and checkUndeclared in flags: if result == nil and checkUndeclared in flags:
fixSpelling(n.sons[1], ident, searchInScopes) fixSpelling(n.sons[1], ident, searchInScopes)
localError(n.sons[1].info, errUndeclaredIdentifier, ident.s) errorUndeclaredIdentifier(c, n.sons[1].info, ident.s)
result = errorSym(c, n.sons[1]) result = errorSym(c, n.sons[1])
elif n.sons[1].kind == nkSym: elif n.sons[1].kind == nkSym:
result = n.sons[1].sym result = n.sons[1].sym

View file

@ -35,7 +35,7 @@ type
errNoneSpeedOrSizeExpectedButXFound, errGuiConsoleOrLibExpectedButXFound, errNoneSpeedOrSizeExpectedButXFound, errGuiConsoleOrLibExpectedButXFound,
errUnknownOS, errUnknownCPU, errGenOutExpectedButXFound, errUnknownOS, errUnknownCPU, errGenOutExpectedButXFound,
errArgsNeedRunOption, errInvalidMultipleAsgn, errColonOrEqualsExpected, errArgsNeedRunOption, errInvalidMultipleAsgn, errColonOrEqualsExpected,
errExprExpected, errUndeclaredIdentifier, errUndeclaredField, errExprExpected, errUndeclaredField,
errUndeclaredRoutine, errUseQualifier, errUndeclaredRoutine, errUseQualifier,
errTypeExpected, errTypeExpected,
errSystemNeeds, errExecutionOfProgramFailed, errNotOverloadable, errSystemNeeds, errExecutionOfProgramFailed, errNotOverloadable,
@ -197,7 +197,6 @@ const
errInvalidMultipleAsgn: "multiple assignment is not allowed", errInvalidMultipleAsgn: "multiple assignment is not allowed",
errColonOrEqualsExpected: "\':\' or \'=\' expected, but found \'$1\'", errColonOrEqualsExpected: "\':\' or \'=\' expected, but found \'$1\'",
errExprExpected: "expression expected, but found \'$1\'", errExprExpected: "expression expected, but found \'$1\'",
errUndeclaredIdentifier: "undeclared identifier: \'$1\'",
errUndeclaredField: "undeclared field: \'$1\'", errUndeclaredField: "undeclared field: \'$1\'",
errUndeclaredRoutine: "attempting to call undeclared routine: \'$1\'", errUndeclaredRoutine: "attempting to call undeclared routine: \'$1\'",
errUseQualifier: "ambiguous identifier: \'$1\' -- use a qualifier", errUseQualifier: "ambiguous identifier: \'$1\' -- use a qualifier",

View file

@ -110,6 +110,7 @@ type
cache*: IdentCache cache*: IdentCache
graph*: ModuleGraph graph*: ModuleGraph
signatures*: TStrTable signatures*: TStrTable
recursiveDep*: string
proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair = proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair =
result.genericSym = s result.genericSym = s

View file

@ -1551,7 +1551,7 @@ proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym =
if isCallExpr(n): if isCallExpr(n):
var expandedSym = qualifiedLookUp(c, n[0], {checkUndeclared}) var expandedSym = qualifiedLookUp(c, n[0], {checkUndeclared})
if expandedSym == nil: if expandedSym == nil:
localError(n.info, errUndeclaredIdentifier, n[0].renderTree) errorUndeclaredIdentifier(c, n.info, n[0].renderTree)
return errorSym(c, n[0]) return errorSym(c, n[0])
if expandedSym.kind notin {skMacro, skTemplate}: if expandedSym.kind notin {skMacro, skTemplate}:

View file

@ -107,7 +107,7 @@ proc lookup(c: PContext, n: PNode, flags: TSemGenericFlags,
var s = searchInScopes(c, ident).skipAlias(n) var s = searchInScopes(c, ident).skipAlias(n)
if s == nil: if s == nil:
if ident.id notin ctx.toMixin and withinMixin notin flags: if ident.id notin ctx.toMixin and withinMixin notin flags:
localError(n.info, errUndeclaredIdentifier, ident.s) errorUndeclaredIdentifier(c, n.info, ident.s)
else: else:
if withinBind in flags: if withinBind in flags:
result = symChoice(c, n, s, scClosed) result = symChoice(c, n, s, scClosed)
@ -195,7 +195,7 @@ proc semGenericStmt(c: PContext, n: PNode,
if s == nil and withinMixin notin flags and if s == nil and withinMixin notin flags and
fn.kind in {nkIdent, nkAccQuoted} and fn.kind in {nkIdent, nkAccQuoted} and
considerQuotedIdent(fn).id notin ctx.toMixin: considerQuotedIdent(fn).id notin ctx.toMixin:
localError(n.info, errUndeclaredIdentifier, fn.renderTree) errorUndeclaredIdentifier(c, n.info, fn.renderTree)
var first = 0 var first = 0
var mixinContext = false var mixinContext = false

View file

@ -143,7 +143,7 @@ proc semBindSym(c: PContext, n: PNode): PNode =
var sc = symChoice(c, id, s, TSymChoiceRule(isMixin.intVal)) var sc = symChoice(c, id, s, TSymChoiceRule(isMixin.intVal))
result.add(sc) result.add(sc)
else: else:
localError(n.sons[1].info, errUndeclaredIdentifier, sl.strVal) errorUndeclaredIdentifier(c, n.sons[1].info, sl.strVal)
proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode

View file

@ -9,8 +9,36 @@ 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``).
Recursive module dependencies are not allowed. This restriction might be mitigated The algorithm for compiling modules is:
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

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

View file

@ -1,15 +1,13 @@
discard """ discard """
file: "mrecmod2.nim" output: "4"
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``
import mrecmod2 # the compiler starts parsing B import mrecmod2 # the compiler starts parsing B
# the manual says this should work
proc main() = proc main() =
var i = p(3) # works because B has been parsed completely here echo p(3) # works because B has been parsed completely here
main() main()

View file

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

View file

@ -29,8 +29,6 @@ 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
----------------- -----------------