Skip to content
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

Qualify some members as scope to avoid deprecation warnings when building with dmd master #2724

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

nordlow
Copy link
Contributor

@nordlow nordlow commented Apr 6, 2023

No description provided.

@nordlow nordlow changed the title WIP: Qualify some members as scope WIP: Qualify some members as scope to avoid deprecation warnings when building with dmd Apr 6, 2023
@nordlow nordlow changed the title WIP: Qualify some members as scope to avoid deprecation warnings when building with dmd WIP: Qualify some members as scope to avoid deprecation warnings when building with dmd master Apr 6, 2023
@nordlow nordlow force-pushed the avoid-scope-deprecations branch 6 times, most recently from 6e7e549 to 6b85bd0 Compare April 12, 2023 13:02
@nordlow nordlow changed the title WIP: Qualify some members as scope to avoid deprecation warnings when building with dmd master Qualify some members as scope to avoid deprecation warnings when building with dmd master Apr 12, 2023
@nordlow
Copy link
Contributor Author

nordlow commented Apr 12, 2023

Ready for review.

@nordlow
Copy link
Contributor Author

nordlow commented Apr 13, 2023

Why is CI jobs waiting for status to be reported, @Geod24? Does #2726 need to merged first?

@s-ludwig
Copy link
Member

CI checks needed to be approved manually.

@@ -165,7 +165,7 @@ struct Bson {
A slice of the first bytes of `data` is stored, containg the data related to the value. An
exception is thrown if `data` is too short.
*/
this(Type type, bdata_t data)
this(Type type, bdata_t data) scope
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly find these annotations quite ridiculous. Why would a struct in D ever have non-scope methods? Since structs can always be moved, storing a pointer to them is never safe outside of a defined scope. Actually, I began fixing similar deprecation warnings before stopping and throwing the diff away, because this just can't be how this is supposed to work.

@Geod24, do you possibly have more insight here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since structs can always be moved, storing a pointer to them is never safe

I'm not 100% sure, but since C++ interop was considered important a few years back (way before all new importC stuff), supporting self-references was considered important enough (e.g. for certain C++ STL data type implementations, but also for certain niche D applications, IIRC Weka), that first opPostBlit - DIP1014 was accepted (but never implemented IIRC), later to be replaced by copy constructors - DIP1018.

I don't if this interaction with DIP1000 was specifically considered, but in general my understanding is that struct moving with self references is supposed to be supported (albeit maybe not fully implemented?).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do copy constructors have any influence on move operations? My impression was that they are purely a generalization of the postblit mechanics. Also, performing a move is specifically done to avoid calling a copy constructor, so that would be a pretty strange choice of semantics.

But apart from the question of always having struct methods be scope, shouldn't scope at least be inferred here?

And then of course also why the compiler thinks that the this pointer is getting escaped by calling a method like this in the first place (which I assumed is the warning that is getting fixed by these changes):

void opAssign(int value)
{
	m_data = toBsonData(value).idup;
	m_type = Type.int_;
}

Copy link
Contributor Author

@nordlow nordlow Apr 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes reduces deprecation warnings in our builds. Can't we just approve these for now. Afaict, it doesn't cost anything.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would a struct in D ever have non-scope methods?

The plan was to also introduce move constructors to enable the use of interior pointers in structs. From that point of view having non-scope struct methods makes sense. Also, I remember that at Weka they used to have a scenario where they would register structs in a global array and perform some operations on them, but because dmd moves structs freely they were having problems and that was what led to the opPostMove proposal (which was abandoned because it has the same flaws as the postblit).

Do copy constructors have any influence on move operations?

The copy constructor, currently, is orthogonal to moving. If you are trying to make a copy of an lvalue, the copy constructor will be called; if you are trying to make a copy of an rvalue, a move is performed (even if the copy constructor is disabled).

But apart from the question of always having struct methods be scope, shouldn't scope at least be inferred here?

In short, if we impose that struct methods are always scope then we basically give up on interior pointers. If we plan on having a move constructor that will finally enable the use of such pointers, then having them by default as non-scope would make sense because if we automatically consider them scope there is no way for the user to mark a method as non-scope. However, indeed, I think that inferring scope for struct methods is the way to go.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so that confirms what I understood of the situation. Under the current regime, a struct shouldn't be able to be @safe and non-scope, so we really shouldn't have this deprecation warning in the first place, regardless of the technical reason why it is triggered here. What I haven't yet understood, though, is why this randomly occurs in some places, but not every time a method is called on a scope struct instance*.

The problem with just going ahead adding scope everywhere is that

  1. this code will in practice linger there basically forever, adding permanent clutter when the fix really should be in the compiler
  2. this is just the tip of the iceberg - there are tons of methods in other places where the same warning occurs, which is why I stopped going this route earlier

Both together make me really reluctant to just follow the warnings here, although I can certainly understand the urge to just get rid of them as quickly as possible.

* If nobody has an explanation for this, I'd start a dustmite run and see where that leads.

Copy link
Contributor Author

@nordlow nordlow Apr 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that we should postpone this until the adjustments to dmd has been performed?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a struct shouldn't be able to be @safe and non-scope

struct S
{
    int s;
    void abc(int i) @safe
    {
        s = i;
    }
}

compiles.

The thing about struct member functions is they are equivalent to a static member function having the first parameter be ref this, as in:

struct S
{
    void abc(ref S this) { ... }
}

If you could give a specific example of a member function that exhibits baffling behavior, I can help.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this will help:

struct S {
    int* p;
    void abc(int i) @safe { }
}

void test() @safe {
    int i;
    S s;
    s.p = &i;
    s.abc(1);
}

Compiling it yields:

test.d(13): Error: scope variable `s` assigned to non-scope parameter `this` calling `abc`

but this compiles without error:

struct S {
    int* p;
    void abc(int i) @safe { }
}

void test() @safe {
    int i;
    S s;
//    s.p = &i;
    s.abc(1);
}

So what's going on? s is a local variable. The member p is a pointer. The pointer is assigned the address of a local variable i. This then makes s a scope variable.

Now s is passed to abc() by ref. The ref prevents the address of s from escaping abc(). But s is wrapping a pointer to a local, so the escape of the contents of s must be protected as well. Making abc() a scope function will accomplish this. Sure enough, adding scope to abc() causes the error to go away.

This is semantically no different from:

@safe void abc(ref int* p, int i) { }
@safe void test() {
    int i;
    int* p;
    p = &i;
    abc(p, 1);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WalterBright, thank you for looking into this! I'll try to look out for places that match this pattern.

I've also seen the Bugzilla issue for rephrasing the warning message, which will help a lot to make it more clear what happens at the innermost place. However, one crucial piece of information still missing from the deprecation message is the instantiation chain, in case the offending function/method is templated. Also, the fact that function/method names are not qualified can make it difficult to to match them against the code base. With all of these fixed, it should be pretty straight forward to find the most appropriate fix.

But in general, the semantics still taste somewhat odd to me. Making scope the default for this feels more like the right approach (using escape or similar to explicitly let the reference escape), but I guess that the way scope propagates to member variables would make that even more more intrusive. I'll have to play more with the current implementation to make any qualified suggestions, though, since I lost track of the developments that came after DIP1000.

data/vibe/data/bson.d Outdated Show resolved Hide resolved
data/vibe/data/bson.d Outdated Show resolved Hide resolved
data/vibe/data/bson.d Outdated Show resolved Hide resolved
data/vibe/data/bson.d Outdated Show resolved Hide resolved
data/vibe/data/bson.d Outdated Show resolved Hide resolved
data/vibe/data/bson.d Outdated Show resolved Hide resolved
Copy link
Contributor Author

@nordlow nordlow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code adjusted for all comments.

data/vibe/data/bson.d Outdated Show resolved Hide resolved
data/vibe/data/bson.d Outdated Show resolved Hide resolved
data/vibe/data/bson.d Outdated Show resolved Hide resolved
data/vibe/data/bson.d Outdated Show resolved Hide resolved
data/vibe/data/bson.d Outdated Show resolved Hide resolved
data/vibe/data/bson.d Outdated Show resolved Hide resolved
@s-ludwig
Copy link
Member

How do you have to compile this to trigger the warnings? I've just fixed some deprecations triggered by formattedWrite in conjunction with a scope range (#2732) and would like to see if the ones here can also be fixed in a more isolated place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants