Skip to content

Commit

Permalink
Report error for foreign forward declared generic (#1374)
Browse files Browse the repository at this point in the history
## Summary

Fix the compiler or backend crashing when using an incomplete generic
procedure from another module. A proper compiler error is now reported
in this case.

Fixes #1368.

## Details

The issues can only occur in the presence of cyclic imports.
Instantiating a generic routine registers it in the instantiating
module, preventing its body from being patched when the generic routine
is complete. Using the instantiation cache when completing a forward-
declared generic won't work, since the module an instantiation comes
from might be closed already.

Instead, this situation is now detected and an error is reported. No
error is reported if no instantiation takes place (i.e., because the
instantiation was cached already), meaning that previously working
code stays working.
  • Loading branch information
zerbina authored Jul 9, 2024
1 parent 91464de commit 8b2b72e
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 0 deletions.
1 change: 1 addition & 0 deletions compiler/ast/report_enums.nim
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ type

rsemCannotInstantiate
rsemCannotInstantiateWithParameter
rsemCannotInstantiateForwarded
rsemCannotGenerateGenericDestructor
rsemUndeclaredField
rsemExpectedOrdinal
Expand Down
4 changes: 4 additions & 0 deletions compiler/front/cli_reporter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,10 @@ proc reportBody*(conf: ConfigRef, r: SemReport): string =
r.ownerSym.name.s
)

of rsemCannotInstantiateForwarded:
result = "cannot instantiate generic procedure forward-declared in " &
"another module"

of rsemTypeKindMismatch:
result = r.str
result.add " got '$1'" % typeToString(r.actualType)
Expand Down
5 changes: 5 additions & 0 deletions compiler/sem/seminst.nim
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,11 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
n[genericParamsPos] = c.graph.emptyNode
var oldPrc = genericCacheGet(c.graph, fn, entry[], c.compilesContextId)
if oldPrc == nil:
if sfForward in fn.flags and fn.itemId.module != c.module.itemId.module:
localReport(c.config, info, reportSem(rsemCannotInstantiateForwarded))
# don't abort instantiation; let it complete for the sake of error
# correction (check/suggest)

# we MUST not add potentially wrong instantiations to the caching mechanism.
# This means recursive instantiations behave differently when in
# a ``compiles`` context but this is the lesser evil. See
Expand Down
5 changes: 5 additions & 0 deletions tests/lang_callable/generics/mgeneric_cycle_forward.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import tgeneric_cycle_forward

forwarded[int]() # was already instantiated; works
# try to instantiate the incomplete generic routine:
forwarded[float]()
20 changes: 20 additions & 0 deletions tests/lang_callable/generics/tgeneric_cycle_forward.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
discard """
description: '''
Ensure instantiating foreign, incomplete generic procedures leads to a
proper error.
'''
errormsg: "cannot instantiate generic procedure forward-declared in another module"
file: "mgeneric_cycle_forward.nim"
line: 5
"""

proc forwarded*[T]()

# instantiate with `int` before starting the import cycle
forwarded[int]()

import mgeneric_cycle_forward # start the recursive import

# complete the forward declaration:
proc forwarded[T]() =
discard

0 comments on commit 8b2b72e

Please sign in to comment.