Issue 15897 - private base class method not seen through derived class
Summary: private base class method not seen through derived class
Status: RESOLVED FIXED
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D2
Hardware: All All
: P1 regression
Assignee: No Owner
URL:
Keywords: pull
: 15983 (view as issue list)
Depends on:
Blocks:
 
Reported: 2016-04-08 13:04 UTC by Steven Schveighoffer
Modified: 2016-09-12 03:36 UTC (History)
5 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Steven Schveighoffer 2016-04-08 13:04:21 UTC
Example (from https://forum.dlang.org/post/jebqwhmasrtzrfhmlzeq@forum.dlang.org)

module a;
import b;

class Animal
{
    private void create() {}
    
}

void foo(Cat cat)
{
    cat.create(); // >> no property create for type 'b.cat'
}

void main() {}

--------------

module b;
import a;

class Cat: Animal {}

Compiles with 2.070

Fails in 2.071.0:

Error: no property 'create' for type 'b.Cat'

If I do this:

void foo(Cat cat)
{
    Animal a = cat;
    a.create();
}

It now compiles. The user shouldn't have to jump through this hoop, the compiler is aware of the access of create via the base class.

If you move Cat into the same module, it also compiles.
Comment 1 Walter Bright 2016-04-15 10:57:05 UTC
Neither are bugs, they are expected behavior.

Ordinarily, this would be removed by making Animal a 'private' base class. But that was removed for reasons I don't understand.
Comment 2 Steven Schveighoffer 2016-04-15 14:50:44 UTC
Can you point me at the rationale for why this change was made?

From your explanation, I don't know if you understand what is happening here.
Comment 3 Martin Nowak 2016-04-16 09:16:07 UTC
Yes I think that should compil, just like this works.
----
private void create(Animal animal) {}

class Animal
{
}

void foo(Cat cat)
{
    cat.create(); // >> no property create for type 'b.cat'
}
----
Comment 4 Steven Schveighoffer 2016-04-16 13:08:06 UTC
(In reply to Martin Nowak from comment #3)
> Yes I think that should compil, just like this works.
> ...
>     cat.create(); // >> no property create for type 'b.cat'

The comment implies that it doesn't compile, but I tested and it does.

Another case:

private void create(Animal animal) { import std.stdio; writeln("ufcs"); }

class Animal
{
    void create() { import std.stdio; writeln("member"); }
}

void main() { foo(new Cat); }

prints: ufcs

In 2.070.2 it prints: member
Comment 5 Walter Bright 2016-04-22 00:17:59 UTC
(In reply to Steven Schveighoffer from comment #4)
> private void create(Animal animal) { import std.stdio; writeln("ufcs"); }
> 
> class Animal
> {
>     void create() { import std.stdio; writeln("member"); }
> }
> 
> void main() { foo(new Cat); }
> 
> prints: ufcs
> 
> In 2.070.2 it prints: member

That's what everyone has been asking for.
Comment 6 Martin Nowak 2016-04-22 07:18:57 UTC
Interestingly this only fails when compiling both modules or only module b, but not when only compiling module a.
It also fails w/ a public instead of a private create method, so this is likely a forward reference issue due to the circular import. At best you resolve the issue by taking the base class (Animal) as parameter to foo, rather than the imported derived class (Cat).
Could anyone Digger the commit that introduced the problem?
Comment 7 Vladimir Panteleev 2016-04-22 07:30:04 UTC
(In reply to Martin Nowak from comment #6)
> Could anyone Digger the commit that introduced the problem?

https://github.com/dlang/dmd/pull/5472
Comment 8 Martin Nowak 2016-05-03 08:28:59 UTC
My initial intuition was wrong here. A private method in the base class should not be visible from a derived class, even in the base class' module.
Even in the base class' module we filter visibility through the derived class for the following reasons.

- You can introduce a create method in the derived class which would not conflict w/ the base class method, but hijack the call in your example.
- The derived class would "look" differently in different modules, depending on all of it's base classes. So far we only make a distinction between the class' module/package and other modules.

To make your code work you have to explicitly convert the derived class to the base class you want to access,

// qualified base class access
cat.Animal.create();

// even better, get rid of the circular import
void foo(Animal animal)
{
    animal.create();
}
Comment 9 Vladimir Panteleev 2016-05-03 08:32:35 UTC
(In reply to Martin Nowak from comment #8)
> A private method in the base class
> should not be visible from a derived class, even in the base class' module.

Why is that? private in D is file-level, not declaration-level.
Comment 10 Vladimir Panteleev 2016-05-03 08:35:29 UTC
The spec says:

"Private means that only members of the enclosing class can access the member, or members and functions in the same module as the enclosing class."

-- https://dlang.org/spec/attribute.html#protection_attributes

TDPL says:

"In all contexts, private has the same power: it restricts
symbol access to the current module (file)."
Comment 11 Vladimir Panteleev 2016-05-03 08:37:38 UTC
(In reply to Vladimir Panteleev from comment #9)
> (In reply to Martin Nowak from comment #8)
> > A private method in the base class
> > should not be visible from a derived class, even in the base class' module.
> 
> Why is that?

Sorry, somehow missed your explanation.

It still contradicts the spec and TDPL though.
Comment 12 Martin Nowak 2016-05-03 09:13:43 UTC
Yeah, we introduced sort of a new concept, "visibility through something", i.e. with the following import chain pkg.A -> B -> pkg.C, pkg.A cannot see package(pkg) protected symbols from pkg.C, even when B publically imports pkg.C.
The same applies for base classes.

Visibility can only be restricted but not be widened later on, which is a fairly intuitive behavior and follows the same principle as the overload resolution w/ mixed protection, making it mostly independent from lookup origin.
http://wiki.dlang.org/DIP22#Description
Comment 13 Martin Nowak 2016-05-03 09:16:00 UTC
https://github.com/dlang/dmd/pull/5727
Comment 14 Kenji Hara 2016-05-04 07:41:43 UTC
(In reply to Martin Nowak from comment #12)
> Yeah, we introduced sort of a new concept, "visibility through something",
> i.e. with the following import chain pkg.A -> B -> pkg.C, pkg.A cannot see
> package(pkg) protected symbols from pkg.C, even when B publically imports
> pkg.C.
> The same applies for base classes.
> 
> Visibility can only be restricted but not be widened later on, which is a
> fairly intuitive behavior and follows the same principle as the overload
> resolution w/ mixed protection, making it mostly independent from lookup
> origin.
> http://wiki.dlang.org/DIP22#Description

How the new concept works for aliased symbols *through* template alias parameters? For example:

module a;
import b;
class C
{
   private int a;
}

void test()
{
   auto c = new C();
   assert(c.a == 0);
   Foo!c.foo();
   assert(c.a == 1);   
}

module b;

template Foo(alias var)
{
    void foo() { var.a = 1; }  // accessing private symbol via alias parameter
}

I thought it could be accepted case, but won't it be accepted anymore?
Comment 15 github-bugzilla 2016-05-06 20:38:28 UTC
Commits pushed to stable at https://github.com/dlang/dmd

https://github.com/dlang/dmd/commit/b6dba3105957cce41ddeb2af77991ef58f82d552
fix Issue 15897 - private base class method not seen through derived class

- properly deprecate private symbol visibility through derived classes

https://github.com/dlang/dmd/commit/c41f00bd16297c5855ed21de72d2e63aa8fc08e5
Merge pull request #5727 from MartinNowak/fix15897

fix Issue 15897 - private base class method not seen through derived class
Comment 16 Kenji Hara 2016-05-07 10:32:38 UTC
The new concept https://issues.dlang.org/show_bug.cgi?id=15897#c12 is completely new thing, so language spec needs to be updated.
Comment 17 github-bugzilla 2016-05-16 02:51:27 UTC
Commits pushed to master at https://github.com/dlang/dmd

https://github.com/dlang/dmd/commit/b6dba3105957cce41ddeb2af77991ef58f82d552
fix Issue 15897 - private base class method not seen through derived class

https://github.com/dlang/dmd/commit/c41f00bd16297c5855ed21de72d2e63aa8fc08e5
Merge pull request #5727 from MartinNowak/fix15897
Comment 18 Sophie 2016-06-08 10:26:24 UTC
Isn't this what the "protected" access modifier is for?
Comment 19 Martin Nowak 2016-09-12 03:20:26 UTC
(In reply to Sophie from comment #18)
> Isn't this what the "protected" access modifier is for?

Yes, protected would be the choice for inheriting a function.

Here the question was whether a private member of a base class, should be visible for derived classes in the base class' module. Not revealing that private member, b/c the derived class is in a different module, is a semantic change introduced by DIP22, hence the deprecation.
Comment 20 Martin Nowak 2016-09-12 03:36:35 UTC
*** Issue 15983 has been marked as a duplicate of this issue. ***