# New Ticket Created by  David 
# Please include the string:  [perl #127172]
# in the subject line of all future correspondence about this issue. 
# <URL: https://rt.perl.org/Ticket/Display.html?id=127172 >


I propose this patch so as to make private attributes user friendly
just like public attributes. With this patch:

- rakudo will automatically generate private accessors when you
declare a private attribute, in the same manner it does when you
declare a public attribute.
- it will not generate the private accessor should a private method
with the same name exist.
- 'is rw' and 'is readonly' now makes sense for private attributes.

The use case is as follows:

class MyClass {
    has $!secret = 5;

    method sauce(MyClass $other) {
        return self!secret() + $other!secret();
    }
}

say MyClass.new().sauce( MyClass.new() );

Instead of having to manually write a private accessor for $!secret,
rakudo now does it for you - user friendly :-)

Patches attached, or can be pulled from
https://github.com/rakudo/rakudo/pull/674
https://github.com/perl6/roast/pull/94

David Ranvig,
>From e07ac0b57b6a9e2b58217354e302469d70ae8325 Mon Sep 17 00:00:00 2001
From: David Ranvig <aut...@gmail.com>
Date: Tue, 5 Jan 2016 19:03:00 +0100
Subject: [PATCH] Add tests for auto-generated private accessors.

---
 S12-attributes/instance.t      | 90 +++++++++++++++++++++++++++++++++++++++++-
 S12-introspection/attributes.t |  5 ++-
 integration/weird-errors.t     | 16 +-------
 3 files changed, 93 insertions(+), 18 deletions(-)

diff --git a/S12-attributes/instance.t b/S12-attributes/instance.t
index 9b6b8c0..2a604e8 100644
--- a/S12-attributes/instance.t
+++ b/S12-attributes/instance.t
@@ -2,7 +2,7 @@ use v6;
 
 use Test;
 
-plan 150;
+plan 169;
 
 =begin pod
 
@@ -716,4 +716,92 @@ throws-like q[class RT74274 { has $!a }; my $a = 
RT74274.new(a => 42);
     ;
 }
 
+# has $!foo declares private readonly accessor
+{
+    my class PrivateAccessor {
+       has $!secret = 42;
+       method peek()       { self!secret(); }
+       method poke($overt) { self!secret = $overt; }
+    }
+
+    my $pa = PrivateAccessor.new();
+    ok(!$pa.can("secret"), 'PrivateAccessor does not have a public accessor');
+    is($pa.peek(), 42, '...but it has a private accessor');
+    dies-ok { $pa.poke(43) }, '...which is a readonly accessor';
+}
+
+# has $!foo is readonly declares private readonly accessor
+{
+    my class PrivateAccessor {
+       has $!secret is readonly = 42;
+       method peek()       { self!secret(); }
+       method poke($overt) { self!secret = $overt; }
+    }
+
+    my $pa = PrivateAccessor.new();
+    ok(!$pa.can("secret"), 'PrivateAccessor does not have a public accessor');
+    is($pa.peek(), 42, '...but it has a private accessor');
+    dies-ok { $pa.poke(43) }, '...which is a readonly accessor';    
+}
+
+# has $!foo is rw declares private rw accessor
+{
+    my class PrivateAccessor {
+       has $!secret is rw = 42;
+       method peek()       { self!secret(); }
+       method poke($overt) { self!secret = $overt; }
+    }
+
+    my $pa = PrivateAccessor.new();
+    ok(!$pa.can("secret"), 'PrivateAccessor does not have a public accessor');
+    is($pa.peek(), 42, '...but it has a private accessor');
+    lives-ok { $pa.poke(43) }, '...which is a rw accessor';
+    is($pa.peek(), 43, '...that works');
+}
+
+# has $!foo does not declare a private accessor if there is already one defined
+{
+    my class PrivateAccessor {
+       has $!secret = 42;
+       method !secret() { $!secret + 1; }
+       method peek() { self!secret(); }
+    }
+
+    my $pa = PrivateAccessor.new();
+    ok(!$pa.can("secret"), 'PrivateAccessor does not have a public accessor');
+    is($pa.peek(), 43, '...the private accessor is not auto-generated');
+}
+
+# has $!foo does declare a private accessor if there is a public one defined
+{
+    my class PrivateAccessor {
+       has $!secret = 42;
+       method secret() { $!secret + 1; }
+       method peek() { self!secret(); }
+    }
+
+    my $pa = PrivateAccessor.new();
+    ok($pa.can("secret"), 'PrivateAccessor does have a public accessor');
+    is($pa.secret(), 43, '...but it is not auto-generated');
+    is($pa.peek(), 42, '...the private accessor is auto-generated');
+}
+
+# Can peruse private accessor within class for objects other than invocant
+{
+    my class PrivateAccessor {
+       has $!secret is rw;
+       submethod BUILD(:$!secret) {}
+       method sauce(PrivateAccessor $other) { self!secret() + $other!secret() }
+       method swap-secrets(PrivateAccessor $other) { (self!secret, 
$other!secret) = ($other!secret, self!secret) }
+       method peek() { $!secret }
+    }
+
+    my $secret1 = PrivateAccessor.new(:secret(5));
+    my $secret2 = PrivateAccessor.new(:secret(9));
+    is($secret1.sauce($secret2), 14, 'PrivateAccessor can access private data 
for other than invocant');
+    lives-ok { $secret1.swap-secrets($secret2) }, '...and can modify the data 
as well since it is declared as rw';
+    is($secret1.peek(), 9, '...secret1 contains secret2');
+    is($secret2.peek(), 5, '...and secret2 contains secret1');
+}
+
 # vim: ft=perl6
diff --git a/S12-introspection/attributes.t b/S12-introspection/attributes.t
index 3247476..4f3eabe 100644
--- a/S12-introspection/attributes.t
+++ b/S12-introspection/attributes.t
@@ -2,7 +2,7 @@ use v6;
 
 use Test;
 
-plan 30;
+plan 31;
 
 =begin pod
 
@@ -34,7 +34,8 @@ ok !@attrs[0].readonly,            'first attribute is not 
readonly';
 
 is @attrs[1].name,         '$!b',   'second attribute had correct name';
 is @attrs[1].type.gist,    '(Int)', 'second attribute had correct type';
-is @attrs[1].has_accessor, False,   'second attribute has no accessor';
+is @attrs[1].has_accessor, False,   'second attribute has no public accessor';
+is @attrs[1].has_private_accessor, True, 'second attribute has private 
accessor';
 ok @attrs[1].build ~~ Code,         'second attribute has build block';
 is @attrs[1].build().(C, $_), 42,
                               'second attribute build block gives expected 
value';
diff --git a/integration/weird-errors.t b/integration/weird-errors.t
index 6ea8f0c..5d43589 100644
--- a/integration/weird-errors.t
+++ b/integration/weird-errors.t
@@ -3,7 +3,7 @@ use Test;
 use lib 't/spec/packages';
 use Test::Util;
 
-plan 20;
+plan 19;
 
 # this used to segfault in rakudo
 #?niecza skip 'todo'
@@ -131,20 +131,6 @@ is_run '{;}',
     },
     'empty code block does not crash (used to do that on JVM)';
 
-# RT #125227
-{
-    my $code = q:to'--END--';
-        class C {
-            has $!x is rw;
-        }
-        --END--
-    is_run(
-        $code,
-        { status => 0, err => -> $o { $o ~~ /useless/ && $o ~~ /':2'/ } },
-        'useless use of is rw reported on meaningful line'
-    );
-}
-
 {
     is_run('(1,2,3).map({ die "oh noes" })',
     {
-- 
2.1.4

>From 3095f286791037672b3377099f54ed2cfccc4919 Mon Sep 17 00:00:00 2001
From: David Ranvig <aut...@gmail.com>
Date: Tue, 5 Jan 2016 18:57:26 +0100
Subject: [PATCH] Implement support for auto-generating private accessors.

'has $!foo' will auto-generate accessors in the same manner as 'has
$.foo' does, but the resulting accessor will be private. No accessor
is auto-generated should a privatly defined method with the same name
already exist. 'is rw' and 'is readonly' now makes sense for private
attributes as well.
---
 src/Perl6/Actions.nqp                          |  1 +
 src/Perl6/Metamodel/BOOTSTRAP.nqp              |  9 ++++++++-
 src/Perl6/Metamodel/EnumHOW.nqp                |  1 +
 src/Perl6/Metamodel/PrivateMethodContainer.nqp |  6 ++++++
 src/core/Attribute.pm                          | 14 ++++++++++----
 src/core/traits.pm                             |  4 ++--
 6 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/src/Perl6/Actions.nqp b/src/Perl6/Actions.nqp
index d659aa6..0d0e0dd 100644
--- a/src/Perl6/Actions.nqp
+++ b/src/Perl6/Actions.nqp
@@ -3102,6 +3102,7 @@ class Perl6::Actions is HLL::Actions does STDActions {
             my %config := hash(
                 name => $attrname,
                 has_accessor => $twigil eq '.',
+               has_private_accessor => $twigil eq '!',
                 container_descriptor => $descriptor,
                 type => %cont_info<bind_constraint>,
                 package => $*W.find_symbol(['$?CLASS']));
diff --git a/src/Perl6/Metamodel/BOOTSTRAP.nqp 
b/src/Perl6/Metamodel/BOOTSTRAP.nqp
index 8964f56..23f2099 100644
--- a/src/Perl6/Metamodel/BOOTSTRAP.nqp
+++ b/src/Perl6/Metamodel/BOOTSTRAP.nqp
@@ -1104,6 +1104,7 @@ BEGIN {
     #     has str $!name;
     #     has int $!rw;
     #     has int $!has_accessor;
+    #     has int $!has_private_accessor;
     #     has Mu $!type;
     #     has Mu $!container_descriptor;
     #     has Mu $!auto_viv_container;
@@ -1119,6 +1120,7 @@ BEGIN {
     Attribute.HOW.add_attribute(Attribute, BOOTSTRAPATTR.new(:name<$!ro>, 
:type(int), :package(Attribute)));
     Attribute.HOW.add_attribute(Attribute, 
BOOTSTRAPATTR.new(:name<$!required>, :type(int), :package(Attribute)));
     Attribute.HOW.add_attribute(Attribute, 
BOOTSTRAPATTR.new(:name<$!has_accessor>, :type(int), :package(Attribute)));
+    Attribute.HOW.add_attribute(Attribute, 
BOOTSTRAPATTR.new(:name<$!has_private_accessor>, :type(int), 
:package(Attribute)));
     Attribute.HOW.add_attribute(Attribute, BOOTSTRAPATTR.new(:name<$!type>, 
:type(Mu), :package(Attribute)));
     Attribute.HOW.add_attribute(Attribute, 
BOOTSTRAPATTR.new(:name<$!container_descriptor>, :type(Mu), 
:package(Attribute)));
     Attribute.HOW.add_attribute(Attribute, 
BOOTSTRAPATTR.new(:name<$!auto_viv_container>, :type(Mu), :package(Attribute)));
@@ -1134,11 +1136,12 @@ BEGIN {
     # Need new and accessor methods for Attribute in here for now.
     Attribute.HOW.add_method(Attribute, 'new',
         nqp::getstaticcode(sub ($self, :$name!, :$type!, :$package!, :$inlined 
= 0, :$has_accessor,
-                :$positional_delegate = 0, :$associative_delegate = 0, 
*%other) {
+                 :$has_private_accessor, :$positional_delegate = 0, 
:$associative_delegate = 0, *%other) {
             my $attr := nqp::create($self);
             nqp::bindattr_s($attr, Attribute, '$!name', $name);
             nqp::bindattr($attr, Attribute, '$!type', nqp::decont($type));
             nqp::bindattr_i($attr, Attribute, '$!has_accessor', $has_accessor);
+           nqp::bindattr_i($attr, Attribute, '$!has_private_accessor', 
$has_private_accessor);
             nqp::bindattr($attr, Attribute, '$!package', $package);
             nqp::bindattr_i($attr, Attribute, '$!inlined', $inlined);
             if nqp::existskey(%other, 'container_descriptor') {
@@ -1188,6 +1191,10 @@ BEGIN {
             nqp::p6bool(nqp::getattr_i(nqp::decont($self),
                 Attribute, '$!has_accessor'));
         }));
+    Attribute.HOW.add_method(Attribute, 'has_private_accessor', 
nqp::getstaticcode(sub ($self) {
+            nqp::p6bool(nqp::getattr_i(nqp::decont($self),
+                Attribute, '$!has_private_accessor'));
+        }));
     Attribute.HOW.add_method(Attribute, 'rw', nqp::getstaticcode(sub ($self) {
             nqp::p6bool(nqp::getattr_i(nqp::decont($self),
                 Attribute, '$!rw'));
diff --git a/src/Perl6/Metamodel/EnumHOW.nqp b/src/Perl6/Metamodel/EnumHOW.nqp
index c3a735d..5b5e017 100644
--- a/src/Perl6/Metamodel/EnumHOW.nqp
+++ b/src/Perl6/Metamodel/EnumHOW.nqp
@@ -8,6 +8,7 @@ class Perl6::Metamodel::EnumHOW
     does Perl6::Metamodel::Stashing
     does Perl6::Metamodel::AttributeContainer
     does Perl6::Metamodel::MethodContainer
+    does Perl6::Metamodel::PrivateMethodContainer
     does Perl6::Metamodel::MultiMethodContainer
     does Perl6::Metamodel::RoleContainer
     does Perl6::Metamodel::BaseType
diff --git a/src/Perl6/Metamodel/PrivateMethodContainer.nqp 
b/src/Perl6/Metamodel/PrivateMethodContainer.nqp
index d201e9d..196535c 100644
--- a/src/Perl6/Metamodel/PrivateMethodContainer.nqp
+++ b/src/Perl6/Metamodel/PrivateMethodContainer.nqp
@@ -21,4 +21,10 @@ role Perl6::Metamodel::PrivateMethodContainer {
             %!private_methods{$name} !!
             nqp::null()
     }
+
+    # Checks if this package (not its parents) declares a given
+    # private method.
+    method declares_private_method($obj, $name) {
+       %!private_methods{$name} ?? 1 !! 0;
+    }
 }
diff --git a/src/core/Attribute.pm b/src/core/Attribute.pm
index 994718c..0c4f48e 100644
--- a/src/core/Attribute.pm
+++ b/src/core/Attribute.pm
@@ -3,6 +3,7 @@ my class Attribute { # declared in BOOTSTRAP
     #     has str $!name;
     #     has int $!rw;
     #     has int $!has_accessor;
+    #     has int $!has_private_accessor;
     #     has Mu $!type;
     #     has Mu $!container_descriptor;
     #     has Mu $!auto_viv_container;
@@ -17,10 +18,11 @@ my class Attribute { # declared in BOOTSTRAP
 
     method compose(Mu $package) {
         # Generate accessor method, if we're meant to have one.
-        if self.has_accessor {
+        if self.has_accessor || self.has_private_accessor {
             my str $name   = nqp::unbox_s(self.name);
-            my $meth_name := nqp::substr($name, 2);
-            unless $package.^declares_method($meth_name) {
+            my $meth_name := nqp::substr($name, 2);        
+           my $has_method = self.has_private_accessor ?? 
$package.^declares_private_method($meth_name) !! 
$package.^declares_method($meth_name);
+            unless $has_method {
                 my $dcpkg := nqp::decont($package);
                 my $meth;
                 my int $attr_type = nqp::objprimspec($!type);
@@ -77,7 +79,11 @@ my class Attribute { # declared in BOOTSTRAP
                         }
                 }
                 $meth.set_name($meth_name);
-                $package.^add_method($meth_name, $meth);
+               if self.has_private_accessor {
+                   $package.^add_private_method($meth_name, $meth);
+               } else {
+                    $package.^add_method($meth_name, $meth);
+               }
             }
         }
 
diff --git a/src/core/traits.pm b/src/core/traits.pm
index e770843..da2cd24 100644
--- a/src/core/traits.pm
+++ b/src/core/traits.pm
@@ -74,11 +74,11 @@ multi sub trait_mod:<is>(Attribute:D $attr, |c ) {
 }
 multi sub trait_mod:<is>(Attribute:D $attr, :$rw!) {
     $attr.set_rw();
-    warn "useless use of 'is rw' on $attr.name()" unless $attr.has_accessor;
+    warn "useless use of 'is rw' on $attr.name()" unless $attr.has_accessor || 
$attr.has_private_accessor;
 }
 multi sub trait_mod:<is>(Attribute:D $attr, :$readonly!) {
     $attr.set_readonly();
-    warn "useless use of 'is readonly' on $attr.name()" unless 
$attr.has_accessor;
+    warn "useless use of 'is readonly' on $attr.name()" unless 
$attr.has_accessor || $attr.has_private_accessor;
 }
 multi sub trait_mod:<is>(Attribute $attr, :$required!) {
     $attr.set_required();
-- 
2.1.4

Reply via email to