-
-
Notifications
You must be signed in to change notification settings - Fork 598
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
Deprecate extern(C++, identifier) #10031
Conversation
CC @TurkeyMan |
7842a1d
to
59b7cc2
Compare
I don't know that Walter's initial implementation is something that people want or not... I mean, I don't use it, but I can only speak for myself on that front. I support this though; I think if we're going to have named scopes, then we should reintroduce the feature as a language feature in its own right. names scopes shouldn't be conflated with |
return null; | ||
return parent.pastMixinAndNspace(); | ||
} | ||
alias pastMixinAndNspace = pastMixin; |
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.
is pastMixinAndNspace even used anymore, it can probably just be removed.
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.
Actually most of the change related to toParent3
can be reverted.
That's quite a lot of changes though, I was considering putting them in an extra commit, what do you think ?
Originally, `extern(C++, ident)` was implemented as introducing a scope, importing C++ semantics into D, and was made a `ScopeDsymbol`. Introducing a scope turned out to be a behavior that was problematic, and users of `extern(C++)` just wanted to use D semantics and have the correct mangling. Later on, `extern(C++, "namespace")` was implemented, which removed the introduction of a scope. However, it was implemented within the existing `Nspace`, basically hacking around the fact that `ScopeDsymbol` introduces a scope and injecting the symbols in the parent scope. This commit implements a new AST node, `CPPNamespaceDeclaration`, which is an `AttributeDeclaration`. `AttributeDeclaration` are the way storage classes (e.g. `extern(C)` and `extern(C++, class|struct)` are implemented, as well as declarations that apply to a scope without introducing one, such as `DeprecatedDeclaration`. Since mangling is part of the function's external interface, CTFE on the message is run during the first semantic pass. Since `_scope` is set earlier than `semantic` is done on the attribute, the full `CPPNamespaceDeclaration` needs to be stored in the `Scope`, not just the resolved `Identifiers`. Moreover, a few tweaks are required to `cppmangle` to make it work with the new AST structure. Once the scoped version of `extern(C++, namespace)` is deprecated, the code can be greatly simplified.
This features tries to import C++ semantics into D, by giving namespaces a scope. However, when it comes to interfacing with C++, we only care about namespaces in the context of mangling: they shouldn't be used for code organization. Namespaces don't work so well for D: for example, namespaces in C++ can be spread across multiple files, which is something D can't and won't do because it conflicts with its very core, modules. It also leads to issues where identifiers used for namespaces (which are usually outside of the programmer's control) end up conflicting with user code. Instead, users should use `extern(C++, "string")`, which makes D code follows D semantic while letting users link with a symbol in a namespace.
59b7cc2
to
0bbd8d9
Compare
Thanks for your pull request, @Geod24! Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub fetch digger
dub run digger -- build "master + dmd#10031" |
No. We've hashed this out endlessly before. If you don't like it, you don't have to use it. |
@WalterBright : Do you actually use this ? |
@Geod24 I might have to for dpp; I'm not sure yet. It's complicated. In any case, since I'm not sure I'm firmly against this because it might make C++ interop harder. |
The problem is this behavior is neither fully C++ nor fully D, if you want to keep namespaces as scopes then make them cross modular like C++ because that's how people will declare and use them when interfacing with C++ code. Half baked features tend to cause more trouble than they solve. |
I've used it when interfacing with C++. I don't deal with C++ the way Manu does.
It was done that way to suggest it is for interfacing to C++.
It's not half baked. The half-baked method is the string one, because it's an ugly hack. For one thing, it introduces a scope and then totally ignores it. |
As I've pointed out on the n.g., the string version is unprincipled and for people who want to organized D modules in an unprincipled manner like C++ code often is. I can't agree that method should be preferred, nor agree that the principled method should be removed. |
So it is half baked. The more you invest in it the more trouble it makes, that's why people should not use it at all.
That was fixed already. #10021. |
I'm not sure I understood what you mean there. |
The description says it is a refactoring. Really, we should not be calling PRs "refactoring" if they have wholesale semantic changes. |
Introducing a scope without introducing a scope is unprincipled. Using strings instead of identifiers is ugly. I don't see where the refactoring introduces a scope. In fact, I don't understand the point of the refactoring. |
That what #10021 changed, it doesn't introduce a scope anymore.
Sorry I wasn't clear, it avoids the old behavior of discarding a scope by not introducing the scope.
It just happened that the identifier version also introduces a scope. Maybe the deprecation could be put on |
Since the
It's unprincipled because it tries to emulate a scope on the C++ side without a scope. If you are dealing with nested/multiple name spaces on the C++ side, that doesn't work on the D side, because the names are not distinguishable. The string version just dumps everything into the D global name space. It's like C macros. No scoping. It's unprincipled. |
That didn't "just happen". It was deliberately designed that way. |
That's not what I meant, I meant the intention was not to undermine the syntax per se but the associated behavior which just happened that way there no reason why the identifier syntax has to introduce a scope. |
The recommendation is to use modules and only modules. There is no concrete support of C++ namespaces i.e. cross files and everything. So if one must group |
There was definitely a reason, which is why I deliberately implemented it that way. Here's the reason: Namespaces in C++ introduce a scope |
Nothing to do with the syntax. |
It is not or I would have implemented it as a struct. Please understand that I've implemented a C++ compiler, I know how namespaces work. By removing the scope on the D side, you cannot have the same name in two C++ namespaces. The POINT of namespaces in C++ is to have scoped lookup. |
That's why D has modules, and this is why D doesn't have namespaces like C++. This feature was supposed to interface with C++ in an idiomatic D manner, not bringing C++ quirks into D, and in this case even with new quirks. |
You can have the same name in two modules, and this is the recommendation anyway. |
Nope:
|
This behavior is the same as on the C++ side. The fact that C++ namespaces introduce a scope is the entire point of them, not a quirk. The above cannot be made to work with the string method. |
Let me fix your example: pragma(mangle, "wow") int foo() { return 1; }
pragma(mangle, "omg") int foo() { return 2; }
static assert(foo() == 1); Nothing about If you want a feature to introduce a named scope, then make a feature to introduce a named scope. That still doesn't specifically have anything to do with |
C++ namespaces introduce a scope. I know you treat all C++ namespaces as being in the same scope, and never write the same name in different namespaces, but you can't generalize that to all C++ programmers. It's not a quirk nor an accident nor my delusion that C++ namespaces introduce a scope. It is that way so the names don't collide. The way you use C++ namespaces might as well be the C way of Insisting that C++ namespaces are mangling only and have no semantic consequences and do not introduce a scope is just plain wrong. It's an objective fact, not my opinion.
It is not what I'd "like". It is the way C++ works. |
If you could expand on the situation where you see this feature as potentially beneficial, I'd be glad to hear it.
Fair enough. Any idea when you will have a clearer picture ?
An objective fact no one but you seem to agree on. Namespaces in C++ can be cross files. Sometimes they serve the purpose that module serve, and for deeply nested one, they serve the purpose of packages. --- a.d
module a;
extern(C++, namespace) void foo(int);
--- b.d
module b;
extern(C++, namespace) void bar(const char*);
--- main.d
module main;
import a, b;
void main ()
{
namespace.bar("Hello world".ptr);
namespace.foo(42);
} At the moment, this errors out:
Of course there is a few simple ways out of this: module main;
import a : foo;
import b : bar;
void main ()
{
bar("Hello world".ptr);
foo(42);
} Which is just... Removing the scope introduced by module main;
static import a;
static import b;
void main ()
{
b.namespace.bar("Hello world".ptr);
a.namespace.foo(42);
// But what's the point of using `x.namespace` when `x` is enough... ?
b.bar("Hello world".ptr);
a.foo(42);
} What if instead my namespaces are declared inside my current module ? module main;
extern(C++, namespace) void foo(int);
extern(C++, namespace) void bar(const char*);
void main ()
{
namespace.bar("Hello world".ptr);
namespace.foo(42);
} Well that's not possible either:
So module main;
extern(C++, namespace) void foo(int);
extern(C++, namespace) void bar(const char*);
void main ()
{
bar("Hello world".ptr);
foo(42);
} Same error:
So the right way to write the function declarations would be: module main;
extern(C++, namespace) {
void foo(int);
void bar(const char*);
}
void main ()
{
bar("Hello world".ptr);
foo(42);
namespace.bar("Hello world".ptr);
namespace.foo(42);
} Wait, what ? I can refer to Oh well, whatever, I'll just bind --- a.d
module a;
extern(C++, std) struct vector(T) { T*[3] data; }
--- main.d
module main;
import a;
import std.stdio;
void main ()
{
std.vector!int vec;
writeln("Pointers: ", vec.data[0], ", ", vec.data[1], ", ", vec.data[2]);
}
What happened here ? Well imported packages (in this case Wait... There's more! module main;
import std.stdio;
extern(C++, std) struct vector(T) { T*[3] data; } This results in the following error:
Because -of course- I am trying to define a symbol which has the same name as a package I import. module main;
import std.stdio;
struct std {} But the key difference here is that the TL;DR: Now go back to all the examples, one by one. Remove the qualification at call site (so |
^^ All of those words. It's a huge mine field, and no D programmer wants added complexity like that. |
I hazard a guess that I'm the only one in this discussion who has read the C++ Standard section on namespaces. Section 3.3.5 Namespace scope and Section 7.3 Namespaces of the C++98 spec. I suggest reading them before telling me C++ namespaces don't have a scope.
The example I gave earlier makes it pretty clear. It works in C++, it fails in D and D code (the string version) does not compile and cannot connect to the C++ version. Feel free to try it yourself. |
Prints |
@Geod24 gave the counter example, which shows why half way implemented features don't work. You are complaining that the new syntax doesn't match C++ namespaces, and we are complaining that neither the old syntax does. At least the for the new syntax people know what to expect, they have to use D modules for grouping symbols. |
Because it's not D. Use modules for grouping in D not namespaces. |
How are you going to do nested namespaces? Manu didn't want to reorganize his code to group all declarations in one namespace together, and didn't want to use alias to merge scopes. But you're suggesting that each namespace go in its own file. I suggested using alias in the n.g. debate on this to Manu, multiple times. It works fine. @Geod24 did not mention this in his rebuttal above. I'm also not suggesting removal of the string namespaces. I gave up on that, even though I consider it wretched and would never use it myself (using alias instead). What I reject is trying to delete scoped namespaces from D, this would be a serious error. |
This is incorrect. Both imports and Template mixins do the same thing. It's a nice convenience feature, and works as long as there are no ambiguities. |
I've never seen code like that in my life. Nobody does that.
How does any D program do it? With nested modules, obviously.
Code is distributed among many headers which gather code in a module-like fashion. No I do NOT want to take 100 header files and collapse them into one module, that's a ridiculous proposition!
This is how D works. We are writing D code. The explicit goal here is to NOT write C++ code.
It's pointless busy work, and it doesn't eliminate the problems, it only mitigates the situation a little bit. Aliasing symbols out of their namespace into the module scope is an admission of defeat, so you've already lost and just forcing boilerplate and busy-work on us at this point... but it also doesn't mean the namespace symbols are not there in the module scope. Import a phobos module, the namespace is still present and it will conflict. There's also still the problem where valid C++ identifiers and valid D identifiers are different sets. We have C++ namespaces with invalid D identifiers. C++ namespaces can't have a D identifier, otherwise you just have a ticking bomb until someone hits a name outside the intersection.
There ARE ambiguities though; STL and phobos, which have a tendency to coexist in every file that has
Personally speaking, I'm not trying to coerce you to do this. I don't care if your scoped namespaces remain; but I do think that if you want a named scope, make a named scope feature. That would be the cool thing to do. |
Also, this is the most uninteresting use of your time imaginable (and ours too). |
BTW, C++ indeed does allow reopening a namespace and inserting more members. The reason is that C++ doesn't do forward references. C++ structs do allow forward referenced members, and doesn't allow reopening the struct. |
There are a million C++ programmers. You haven't seen a remote percentage of the code they write. I invite you to submit a proposal to the C++ Standards Committee to remove namespace scopes and make it mangle-only. You'll find out real quick who relies on it.
Import the 100 header files into one module, and add 100 aliases to that module, and you're done. And you've got a principled solution.
That's a general issue, not specific to namespaces, and is not a scoping thing.
I get triggered when features get deleted due to resolute misunderstanding. |
And how many of them are here investing years of their life trying to be successful with D? What we have is a small sample of REAL ACTUAL USERS, they are all in agreement here, and couldn't be clearer about the tool we need. Focus on the people that matter, and if someone comes along in the future and says "I want a namespace scope", talk to them and hear their story... Don't disregard the people standing right here while saying some bullshit about a million people that don't give a damn.
I'm not deleting it, I'll leave that for others to argue about. It doesn't affect me, but I am disappointed that it's not just a bonafide named scope in its own right, that might be generally useful on occasion.. |
This works:
|
This also works:
It's no worse than writing headers in C++. |
Because you are not using their qualified name... As @TurkeyMan said:
It does. And so does its string equivalent: --- a.d
module a; extern (C++, `ns`) { int foo() { return 1; } }
--- b.d
module b; extern (C++, `ns`) { int bar() { return 2; } }
--- ns.d
module ns;
public import a, b;
--- main.d
import ns;
static assert(ns.foo() == 1 && ns.bar() == 2); So what's the point of introducing a scope just to remove it because it always conflict ?
C++ is not the baseline here, D is. |
FYI, there are a lot of things where D doesn't match the other language in a language integration. Examples:
Just to mention a few. |
Based on #10021 . Only the last commit is should actually be part of this PR.