mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-01 19:33:42 +00:00
IC: export s makes s importable even without a * on its declaration
`nim c --ic:on` could not compile anything that reached `std/tempfiles`:
lib/std/tempfiles.nim(115, 38) Error: type mismatch
Expression: initRand()
Expected one of: [1] proc initRand(seed: int64)
That is `std/random`. It declares `proc initRand(): Rand` WITHOUT a `*`
and then, forty lines later, `since (1, 5, 1): export initRand`.
Two mechanisms make a symbol importable and the writer only knew one.
`sfExported` is the `*` on the declaration; `semExport` instead calls
`reexportSym`, which adds the symbol to the module's interface table and
sets no flag. `writeSymDef` decided the NIF `x` marker — "importable as a
bare identifier" — from `sfExported` alone, so a symbol exported the
second way shipped as private and its importer reported an undeclared
identifier. Nothing about this is IC-specific in the source; it is
ordinary stdlib code that classic compilation accepts.
So ask the interface too: `reexportedLocalSyms` collects the symbols a
module defines that reached its interface without the flag, and they get
the marker. Symbols that are genuinely private still do not — the
regression test's sibling case (`proc g()`, never exported) is still
rejected under both `--ic:on` and `--ic:off`.
This was found by pointing `--ic:on` at Atlas, which is now the first
sizeable third-party program it builds. `nim c --ic:off` and `--ic:on`
produce the same working binary from the same 209-module closure.
ic 41/41 (the new test is the 41st), `koch boot -d:release` equal
executables.
This commit is contained in:
@@ -277,6 +277,9 @@ type
|
||||
inTypeReclist: int # >0 while writing a type's OWN reclist: fields must be SELF-CONTAINED
|
||||
# defs (the type can be seek-loaded in isolation), not entry-deduped uses
|
||||
emittedCanonTypes: Table[string, int32] # canonical type name -> itemId.item of the def
|
||||
extraExports: HashSet[ItemId] # symbols made importable by an explicit `export s`
|
||||
# rather than by a `*` on the declaration; see
|
||||
# `modulegraphs.reexportedLocalSyms`
|
||||
|
||||
|
||||
when defined(icLocalSymStats):
|
||||
@@ -1106,8 +1109,13 @@ proc writeSymDef(w: var Writer; dest: var IcBuilder; sym: PSym) =
|
||||
# ("undeclared field 'Number'").
|
||||
let isPureEnumField = sym.kindImpl == skEnumField and sym.typImpl != nil and
|
||||
sym.typImpl.symImpl != nil and sfPure in sym.typImpl.symImpl.flagsImpl
|
||||
# `sfExported` is the declaration's `*`. An explicit `export s` makes a symbol
|
||||
# importable WITHOUT it (semExport -> reexportSym -> the interface table only),
|
||||
# so ask the interface as well or those symbols ship as non-importable and the
|
||||
# importer reports "undeclared identifier".
|
||||
if sym.kindImpl != skField and not isPureEnumField and
|
||||
{sfExported, sfFromGeneric} * sym.flagsImpl == {sfExported}:
|
||||
({sfExported, sfFromGeneric} * sym.flagsImpl == {sfExported} or
|
||||
sym.itemId in w.extraExports):
|
||||
dest.addIdent "x"
|
||||
else:
|
||||
dest.addDotToken
|
||||
@@ -2188,8 +2196,10 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
|
||||
resolvedImportDeps: seq[FileIndex] = @[];
|
||||
firstUnusedId: int32 = 0;
|
||||
expansions: seq[(PSym, TLineInfo)] = @[];
|
||||
moduleFlags: int32 = 0) =
|
||||
moduleFlags: int32 = 0;
|
||||
extraExports: seq[ItemId] = @[]) =
|
||||
var w = Writer(infos: newLineInfoWriter(config), currentModule: thisModule)
|
||||
for id in extraExports: w.extraExports.incl id
|
||||
w.deps = newIcBuilder(64)
|
||||
var content = newIcBuilder(300)
|
||||
|
||||
|
||||
@@ -315,6 +315,23 @@ proc reexportedModuleSyms*(g: ModuleGraph; m: PSym): seq[(string, string)] =
|
||||
not seen.containsOrIncl(s.position):
|
||||
result.add (s.name.s, cachedModuleSuffix(g.config, FileIndex s.position))
|
||||
|
||||
proc reexportedLocalSyms*(g: ModuleGraph; m: PSym): seq[ItemId] =
|
||||
## Symbols DEFINED in `m` that reached `m`'s interface through an explicit
|
||||
## `export s` rather than through a `*` marker on their declaration.
|
||||
##
|
||||
## `semExport` re-exports by `reexportSym`, which adds to the interface table
|
||||
## and does NOT set `sfExported` — so a symbol can be importable while its
|
||||
## declaration says otherwise. The NIF writer decides importability from
|
||||
## `sfExported` alone and therefore missed exactly these. `std/random` does it
|
||||
## (`proc initRand(): Rand` private, then `since (1, 5, 1): export initRand`),
|
||||
## which is why `--ic:on` could not compile anything that reached
|
||||
## `std/tempfiles` — `initRand()` was undeclared in the importer.
|
||||
result = @[]
|
||||
for s in g.ifaces[m.position].interf.data:
|
||||
if s != nil and s.kind != skModule and sfExported notin s.flags and
|
||||
s.itemId.module == m.position:
|
||||
result.add s.itemId
|
||||
|
||||
proc someSym*(g: ModuleGraph; m: PSym; name: PIdent): PSym =
|
||||
let importHidden = optImportHidden in m.options
|
||||
result = strTableGet(g.ifaces[m.position].interfSelect(importHidden), name)
|
||||
|
||||
@@ -339,7 +339,8 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog,
|
||||
replayActions, implDeps, reexportedModuleSyms(graph, module),
|
||||
genericOffers, typeOffers, resolvedImportDeps, firstUnusedId,
|
||||
expansions, moduleFlags)
|
||||
expansions, moduleFlags,
|
||||
reexportedLocalSyms(graph, module))
|
||||
# The module's REAL direct imports (incl. macro-generated) for `nim ic`'s
|
||||
# graph re-derivation; see ast2nif.writeSemDeps / semdata.addImportFileDep.
|
||||
var semDepPaths: seq[string] = @[]
|
||||
|
||||
4
tests/ic/mexportprivate.nim
Normal file
4
tests/ic/mexportprivate.nim
Normal file
@@ -0,0 +1,4 @@
|
||||
proc pub*(x: int): int = x + 1
|
||||
|
||||
proc hidden(): int = 42 # no `*` ...
|
||||
export hidden # ... but explicitly re-exported
|
||||
14
tests/ic/texportprivate.nim
Normal file
14
tests/ic/texportprivate.nim
Normal file
@@ -0,0 +1,14 @@
|
||||
discard """
|
||||
output: '''42'''
|
||||
"""
|
||||
|
||||
# `export s` re-exports a symbol whose declaration has no `*`. It reaches the
|
||||
# module interface through `reexportSym` alone, so a NIF writer that decides
|
||||
# importability from `sfExported` ships it as private and the importer reports
|
||||
# "undeclared identifier". `std/random` does exactly this
|
||||
# (`proc initRand(): Rand` + `since (1, 5, 1): export initRand`), which made
|
||||
# `--ic:on` unable to compile anything reaching `std/tempfiles`.
|
||||
|
||||
import mexportprivate
|
||||
|
||||
echo hidden()
|
||||
Reference in New Issue
Block a user