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
Fix issue 313 & 314 implement more strict import mechanism #2256
Conversation
Could you show the code example for this? |
|
What if you want to disambiguate between a local symbol and an import? import bar;
void main()
{
struct Bar { void foo() { } }
auto bar = Bar();
.bar.foo(); // don't call instance, but a global foo function
} I don't think it's a good idea to start breaking code like this. |
You can use renamed import for the case. Essentially, module scope operator is for accessing names declared in "module scope level". However, in your case, the 'bar' is a package name, and it is not a member of the module. module foo;
import bar;
void main() {
.bar.baz();
// means accessing a 'bar' _declared in the module level scope of 'foo'_.
// So inherently it is equivalent with 'foo.bar.baz()'
} The behavior was the unintended behavior which had came from wrong compiler implementation. We should not keep it. |
On Tue, Jun 25, 2013 at 06:38:17AM -0700, Hara Kenji wrote:
For me is not that clear that "module scope level" mean only stuff |
@leandro-lucarella-sociomantic Because there's no consistent semantics to support it properly. It's completely wrong behavior which dependent on current dmd front-end implementation. Keeping non-descriptive behavior will stop writing another D front end. |
On Tue, Jun 25, 2013 at 08:03:32AM -0700, Hara Kenji wrote:
What do you mean by "no consistent semantics to support it properly"? I |
For example, currently dmd accepts this: import std.stdio;
void main() { .std.stdio.writeln(); } However this is rejected. void main() {
import std.stdio;
.std.stdio.writeln();
} One another case. If a module 'a' exists: module a;
static import std.stdio; Current dmd accepts this: module b;
import a;
void main() { .std.stdio.writeln(); } But rejects: module b;
static import a;
void main() { .std.stdio.writeln(); } I couldn't look any "consistent" semantics there. |
All of these samples you posted make sense to me. I've always looked at // #2 here
void foo()
{
.bar(); // #1 pretend we're at location #2, and look for 'bar' there
} |
The fact that the current state of the compiler is broken it doesn't mean we can't give it a consistent semantic. I also had the same semantic @AndrejMitrovic mentioned in mind, and I think is consistent and very useful. A case where I used it is to wrap C libraries, a very common pattern is (extremely simplified): import std.stdc.posix.unistd;
class File
{
int read(size_t bytes)
{
return .read(bytes):
}
} Is not that I couldn't live with it, what I'm saying is this used to work, and is perfectly fine and clear what the intention is, so why should we break it? Again, if keeping the right semantics is extremely hard implementation-wise, I can understand your desire to drop it, but so far you never said it was an implementation problem. |
@leandro-lucarella-sociomantic This change does not break your case. In module scope, import std.stdc.posix.unistd;
class File
{
int read(size_t bytes)
{
return .read(bytes): // still OK
return std.stdc.posix.unistd.read(bytes): // still OK
return .std.stdc.posix.unistd.read(bytes): // Disabled by this change
}
} Note the thing disabled by this is: "Module Scope Operator with fully qualified name". |
On Tue, Jun 25, 2013 at 09:26:07AM -0700, Hara Kenji wrote:
OK, please ignore my comments then, that seems to be an extreme corner |
I've changed my mind about this a couple of times. struct std {
struct stdio {
static void writeln() {}
}
}
void bar() {
.std.stdio.writeln();
} This does not currently compile. void bar()
{
struct std {
struct stdio {
static void writeln() {}
}
}
.std.stdio.writeln();
} I think the import should behave in exactly the same way as the struct declaration. One indication that they should be the same is that you cannot have the import and the struct declaration in the same scope, otherwise you get an error like: foo.d(3): Error: struct foo.std conflicts with import foo.std at foo.d(1) |
I don't agree that the existing behavior is inconsistent or wrong. The leading '.' means lookup the name starting at module scope. All the examples follow the rules of that. |
@donc , I have completely opposite opinion. A package/module name which introduced by I'm understanding that the language semantic that you and today's dmd front-end would intend - that's just a single namespace shared by all symbols in a scope. I can agree that's a consistent semantic, but at the same time, I must say it would be unsuitable and difficult to understand. In other words, current intrusive module foo;
void foo() {}
void test1() { foo(); } // prefer function name module bar;
import foo;
void test2() { foo(); } // prefer module name - OOPS! Currently, in module 'bar', Instead, I think that import mechanism should only affect symbol visibility. When an idenfier After all, a properly declared symbol (aggregates, functions, templates, and variables) should be always preferred than package/module names in symbol searching. To do that, each scope should have two name spaces - one is for declared symbols in the scope, and another is for imported package/module names. This PR implements this by |
On Wed, Jun 26, 2013 at 02:46:55AM -0700, Hara Kenji wrote:
I don't agree with this. I think is less safe than making them clash. import foo; // bar is also present here
void bar() {}
void f() { bar(); } They you want to remove bar() from the module and forget to update f(): import foo; // bar is also present here
void f() { bar(); } Now this is still compiling and calling foo.bar()! |
@leandro-lucarella-sociomantic That's not a problem. When you import a module |
@WalterBright Yes, current behavior is not a bug from the view of current implementation. BUT, that's very weird and hard to understand. Honestly I couldn't understand the correct import mechanism behavior until looking at dmd code. I believe that most programmers would imagine that "import" would provide a way to control symbol visibility. But contrary to that, current D's import is intrusive to the importing scope. If import std.algorithm; // module scope import
class C { import std.stdio; } // class scope import
void main() {
import std.stdio; // function local import
.std.algorithm.map(a=>a)([1,2,3]; // currently OK
.std.stdio.writeln(); // NG
// -> what is the "fully qualified name"?
C.std.stdio.writeln(); // currently OK
// 'std' is package name, but it looks like declared in class C. Strange!
} I argue that the current behavior is just debt anymore. In D1 age, it was reasonable for "easy-implementable language". However in D2, that's just an unacceptable behavior to me. |
Updated commits for these issues.
|
What's weird and hard to understand? The whole point of the dot syntax is to disambiguate between local and module-scoped symbols. If you can't even do that anymore then the dot syntax completely loses it's purpose. |
@AndrejMitrovic I have no ambiguity about the dot syntax (== Module Scope Operator) semantics. The thing I have been difficult to understand was why |
You've confused me a little bit with the samples (not knowing whether something is the current behavior or future behavior): import std.algorithm;
void main()
{
.std.stdio.writeln(); // currently compiles
} I have no idea why that works now, I think it should fail. As for this: void main()
{
struct S
{
import std.stdio;
}
S.std.stdio.writeln();
} This currently compiles, but I don't think I've ever used it (I doubt anyone else has either). Local imports are a pretty recent feature. I don't have a strong opinion about this case. With that being said, here is the current behavior I want to keep: module test;
import std.stdio;
@disable void writeln() { }
void main()
{
struct std
{
struct stdio
{
@disable static void writeln() { }
}
}
.std.stdio.writeln(); // OK, grabs the phobos writeln function
writeln(); // calls @disable function, fails compilation
.writeln(); // ditto
std.stdio.writeln(); // ditto (inner struct function)
} |
This PR will reject
Yes, it also be rejected.
It would fail to compile, as same as the first case.
Exactly. |
Why would it fail? It's explicitly calling the writeln in the phobos library. |
|
Look at the documentation:
That last part says everything: look up the name at the module scope level. It doesn't say "look up the symbol defined in the module." |
@AndrejMitrovic , thanks for explanation. OK, I was completely misunderstanding about that. |
{ | ||
Import *imp = (*cd->imports)[j]->isImport(); | ||
PROT prot = cd->prots[j]; | ||
if (imp && prot == PROTpublic || prot == PROTprotected) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be parenthesized? if (imp && (prot == PROTpublic || prot == PROTprotected))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch! Will fix.
But that was the whole point of the design. It is not broken, and it is used. |
This is an additional breaking change. S.s means the same as b76.S, and there is no ambiguity. |
@WalterBright the point was that selective import should make symbol visible, not add an extra alias that affects the overload set. |
@blackwhale this is not a selective import, it's a renamed import. And its point absolutely is to create another name for it that exists in the current scope, and that is exactly what it's behavior is and is used for. |
On Tue, Mar 18, 2014 at 08:22:58AM -0700, Hara Kenji wrote:
I think this is one of those breaking changes people are willing to Having a clear and painless migration path will be highly appreciated |
No. |
No, because it broke code that has come from users, and I expect there's much more of that out there.
313 and 314 are about respecting private declarations, they are not about changing the lookup rules. I confess I don't even understand what the new rules are, let alone how to fix code so it works under the new rules. In the example I posted here, I don't know how to fix it for the new rules. I don't understand Kenji's explanation. |
I suspect that even if I was wholly in favor of this PR, we would be forced to back it out if we tried to release this. People make complicated use of import declarations, and they are not at all going to be happy about having to reengineer them. |
On Wed, Mar 19, 2014 at 10:07:47AM -0700, Михаил Страшун wrote:
I guess you are referring to the alias part of the change to |
Can you be more specific? |
On Wed, Mar 19, 2014 at 03:41:40PM -0700, Walter Bright wrote:
OK, maybe I'm misunderstanding the issue here, I'm just talking about // explicit private no longer needed but added for clarity private import std.stdio;In file b.d:import a; void main() { }// explicit privates unnecessary but added for clarity version(sele) private import std.stdio : writefln;In file b.d:import a; void main() { } |
This reverts most of commit 6cf3992
Revert "Merge pull request #2256 from 9rnsr/fix_imports"
I opened #3407. |
I don't think that's wrong code. It's wrong code if you use |
Import declaration docs think it is (see "Renamed Imports"). |
I am with @AndrejMitrovic here, looks more like bug in spec than in behavior. Otherwise |
I agree with Denis, to me it's natural that renamed imports don't add the raw symbols to the importing scope. After all, you use renamed imports precisely if you want to control the availability of symbols. Requiring the use of |
I somehow thought it was existing behavior. Well, it could go either way as far as I'm concerned.. |
Issue 313 - [module] Fully qualified names bypass private imports
Issue 314 - [module] Static, renamed, and selective imports are always public
Changed points:
Module Scope Operator with fully qualified name no longer works.