-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[superseded] import foo {.all.}
#11865
Conversation
d6a6b68
to
c9d159c
Compare
This is excellent, I've always wanted a way to keep symbols private but was restricted due to the common Why the push/pop pattern while we could just |
@zah Since this PR aims to fix your problem, I would like to have your opinion on it. |
|
We should make an attempt to make Would the grammar need to be modified to reflect this change? I don't recall pragmas on imports anywhere else in the language... |
I didn't have to change the parser as you can see from the PR, so I don't think grammar (nor auto generated |
I can't say I like this feature. But the use case for unit testing private functions is in my opinion a valid use case example. But I think maybe it is a better solution to have a special solution that just fixes unit testing without being so disruptive to the language. |
The only alternative is to do unit-testing within the source file. Beyond unit-testing it also address why people would want recursive imports: because with a common |
You'll still be able to access anything from |
@GULPF thanks for pointing out the dangers of features like this. |
|
I thought about this and I'm not certain that it does solve the desire for recursive imports completely: don't you still need to have a module specifically for code shared between multiple imports (essentially a reduced |
c9d159c
to
182de70
Compare
I disagree, this PR indeeed offers a more targetted violation of visibility. With With this PR, user can do:
And allowing raw field access is the point: this lets user access raw fields (eg for serialization / debugging / etc) as escape hatch to bypass restrictions from public API (which may not have thought about your use case when it was designed). The getters/setters can still be used explictly, via
A special solution wouldn't help with the other use cases mentioned besides unit testing.
I'm not seeing how that is disruptive, given that a user can already bypass visibility restrictions with
|
@krux02, my opinion is explained in significant detail in the linked thread: https://forum.nim-lang.org/t/4293#26876 This opinion is considered controversial, but I think the best way to solve the package-level visibility issues is to work on supporting cyclic imports. Private imports or other more precise ways to control the visibility in packages tend to work more as a band-aid for the original root problem. |
Compilation time keeps getting worse. I hope that whatever solution we adopt does not reduce it further. In the forums, Zah discusses cyclic dependencies, which C++ partially solves via forward-declared references. But those are also used simply to reduce compilation time. In Go, everything in the same "package" (i.e. directory) can access each other's privates. In Nim, you could combine all files in a directory into a single file. I'm wondering: Is that sufficient to solve cyclic dependencies? In other words, do completely separate directories/modules/whatever really require cyclic dependencies? I wouldn't mind a Go-style solution, where separate files are possible but they act as if they are a single file in that directory. Or maybe we need to look more closely into "modules", e.g. the new-ish Go modules or C++17 (which improves compilation speed significantly). |
It seems to me that |
What I would like in auxiliary to this is some way of controlling doc generation: if you have a module that you want to exclusively private-import, it would be useful to be able to have some directive to not generate docs for that module. |
that was the 1st thing I had tried, but I gave up with that approach as it just doesn't work in practice (see example below):
# top/main.nim
import top/bar/foo
let t = foo.fun1() # BUG: undeclared identifier since foo was patched as `foo2`
echo t.x # BUG: patchFile won't work with private fields
# top/bar/foo.nim
import ./fooRelative # BUG: relative imports won't work
type A = object
x: int
proc fun1(): A = discard
# top/overrides/foo2.nim
include top/bar/foo # BUG: Error: recursive dependency
export A
export fun1
# config.nims
patchFile("top", "foo", "top/overrides/foo2.nim") this PR has none of those issues and just works |
182de70
to
f7c41f5
Compare
I've added a large test suite (which explains why there are many files in this PR; see updated top-level post for details); this feature seems to work in all cases: these all work as you'd expect with this feature: |
bc67e51
to
1a2148f
Compare
Also, had a question: can you private-import using |
not implemented but not hard to add, I can add that in a follow-up PR after this one is merged |
Cyclic imports are more important and could be more beautiful than this rather ugly solution. And before we have a half-working version of incremental compilation (IC) we shouldn't add cyclic imports either. So I'm afraid this PR is stuck until IC is in beta-quality shape. Worse than that, the feature doesn't allow anything that's currently impossible. Yes, public fields are suboptimal, but who can judge at this point that |
So I can do this? from foo {.all.} import nil That's mainly what I would do. |
Possibly a redundant argument over simply "more descriptive", but In that light, My idea of |
c27e3e0
to
e7fd8ff
Compare
@Araq PTAL
EDIT: And also the fact that the same pragma can be reused for a future
yes, see test suite ( from https://forum.nim-lang.org/t/7193#45495
no, it's I've now added a test for that, see |
Ok. I agree per-import-argument repetition is more flexible. I guess that sinks my What about re-export via Also, you should probably rename your tests/ subdir to like |
It's easy to write
I added a test, see import ./m3 {.all.}
export m3 # only public
# export m3 {.all.} # xxx this can be supported in future
export m3h3 # private m3.m3h3 is in scope, so its fair game to re-export
export m3.m3h4 # ditto
done |
Needs an RFC and also probably will linger in limbo for a bit because I want to merge my araq-ic4 branch first which touches the same modules... |
import foo {.privateImport.}
import foo {.all.}
done, see nim-lang/RFCs#299
Note that this PR has been ready since july 2019 and rebased many times to fix bitrot conflicts. I would really like to see this feature being merged sooner rather than later. |
…) works; import foo/bar {.privateImport.} works (ie works with infix modules, and works even without module alias as
5404604
to
7bdd6e4
Compare
EDIT: formerly was {.privateImport.}, I renamed it to {.all.}
proposal to solve Does Nim need package-level visibility?
(I had originally proposed this here:
import foo {.private.}
to allows access to private fields )Will add unittests later (I worked on a test suite)EDIT: I've added extensive unittests (13 files to try all possible situations including declared/compiles/import as/from/field access etc ; actually these were generated from a single file in HAR format [1] that is expanded automatically). Note: this results in a single compilation (
tmain.nim
) so this is fast to compile/run.it does work, feel free to try it out
use cases
would fix Enable cross-module initialization of private fields RFCs#145
when a 3rd party library didn't expose a symbol that you need, you can use this (I keep running into this kind of issue)
this obviously can break if the 3rd party library then changes its implementation (eg renames/removes a private field), but you need to know what you're doing when using this feature. Note that
include
would have the same problem, but worse: since the include is more invasive than something like:from foo {.privateImport.} import bar
which is a much more targeted violation of visibility)The alternative is to patch the 3rd party library (eg: nim compiler code itself, or any other nimble package) by making some private symbols public, however this is worse (eg the private to public change can have un-intended side effects and would affect all clients, whereas with
privateImport
the field is only accessed in a targeted, easy to audit, place;Furthermore waiting for 3rd party library to accept the change can take a while (or never happen), and maintaining a separate fork of the 3rd party library will cause even more issues (especially if project is to be shared).
when organizing your code, (eg splitting out a types module to overcome lack of cyclic imports), it's a very common problem that the splitted module has to export more things than would be ideal, so that these symbols can be accessed. this PR solves this problem, allowing to make more symbols private, and override visibility with
privateImport
misc use cases, eg for a testing framework, to test private functions (that shouldn't be part of public API)
helps with serialization (private fields) and reflection
for debugging: i use this a lot as well: it allows writing a tool to call an arbitrary (eg private) proc with specified runtime args, without having to modify source code; using include for that has similar issues as mentioned below
reduce need for
include
improve error messages now that we have more context, eg:
undeclared bar
=>undeclared bar: foo.bar is private
unbloat system.nim
eg: [superseded] fix #12996 #12997 (comment)
from system {.privateImport.} import since
why not use include?
while
include
has its use cases, theprivateImport
feature from this PR is in general a much better alternative that doesn't break modularity:C #include
nim doc
,currentSourcePath
, nimrtl which exports unmangled proc names, so also runs into above issuesnim doc lib/std/sha1.nim
works butnim doc lib/deprecated/pure/securehash.nim
fails with: Error: redefinition of 'secureHash' (securehash.nim includes sha1)import pragma
importPrivate foo
), as it makes better code reuse and provides orthogonal functionality; there are other use cases for import pragmas, eg this PR Experimental typeImports #8660 (Experimental typeImports) could've used a pragma instead, eg:from colors import Color {.typeImports.}
; and this too:import std/json {.undef:js.}
as suggested herenim js
prevents valid compile time functionality #11988[1] for details on HAR see https://github.com/marler8997/har (a human readable multifile archive similar to org jointly proposed with @marler8997, which I've since reimplemented in nim); the file that generated these tests looks like this: https://github.com/timotheecour/vitanim/blob/master/testcases/har/test1.har
note
keeps coming up, eg: