File Coverage

blib/lib/mop/traits.pm
Criterion Covered Total %
statement 98 98 100.0
branch 33 42 78.5
condition 14 32 43.7
subroutine 30 30 100.0
pod 9 11 81.8
total 184 213 86.3


line stmt bran cond sub pod time code
1             package mop::traits;
2              
3 143     143   1716 use v5.16;
  143         453  
  143         5764  
4 143     143   768 use warnings;
  143         234  
  143         5486  
5              
6 143     143   1079 use Scalar::Util 'blessed', 'weaken';
  143         262  
  143         334305  
7              
8             our $VERSION = '0.03';
9             our $AUTHORITY = 'cpan:STEVAN';
10              
11             our @available_traits = qw[
12             rw
13             ro
14             required
15             weak_ref
16             lazy
17             abstract
18             overload
19             extending_non_mop
20             repr
21             ];
22              
23             require Carp;
24              
25             sub setup_for {
26 168     168 0 411 my ($pkg) = @_;
27              
28             mop::internals::util::install_sub($pkg, 'mop::traits', $_)
29 168         883 for @available_traits;
30             }
31              
32             sub teardown_for {
33 6     6 0 16 my ($pkg) = @_;
34              
35             mop::internals::util::uninstall_sub($pkg, $_)
36 6         25 for @available_traits;
37             }
38              
39             sub rw {
40 24     24 1 43 my ($attr) = @_;
41              
42 24 50 33     288 die "rw trait is only valid on attributes"
43             unless blessed($attr) && $attr->isa('mop::attribute');
44              
45 24         95 my $meta = $attr->associated_meta;
46 24         87 weaken(my $weak_attr = $attr);
47             $meta->add_method(
48             $meta->method_class->new(
49             name => $attr->key_name,
50             body => sub {
51 36     36   2026 my $self = shift;
52 36 100       133 $weak_attr->store_data_in_slot_for($self, shift) if @_;
53 36         127 $weak_attr->fetch_data_in_slot_for($self);
54             }
55             )
56 24         524 );
57             }
58              
59             sub ro {
60 53     53 1 98 my ($attr) = @_;
61              
62 53 50 33     588 die "ro trait is only valid on attributes"
63             unless blessed($attr) && $attr->isa('mop::attribute');
64              
65 53         223 my $meta = $attr->associated_meta;
66 53         169 weaken(my $weak_attr = $attr);
67             $meta->add_method(
68             $meta->method_class->new(
69             name => $attr->key_name,
70             body => sub {
71 120     120   28165 my $self = shift;
        109      
        83      
        66      
        43      
        16      
72 120 100       365 die "Cannot assign to a read-only accessor" if @_;
73 118         419 $weak_attr->fetch_data_in_slot_for($self);
74             }
75             )
76 53         199 );
77             }
78              
79             sub required {
80 6     41 1 11 my ($attr) = @_;
81              
82 6 50 33     78 die "required trait is only valid on attributes"
83             unless blessed($attr) && $attr->isa('mop::attribute');
84              
85 6 100       24 die "in '" . $attr->name . "' attribute definition: "
86             . "'required' trait is incompatible with default value"
87             if $attr->has_default;
88              
89 5         37 weaken(my $weak_attr = $attr);
90             $attr->set_default(sub {
91 2     27   6 Carp::croak("'" . $weak_attr->name . "' is required")
92 5         49 });
93             }
94              
95             sub abstract {
96 14     41 1 27 my ($class) = @_;
97              
98 14 50 33     177 die "abstract trait is only valid on classes"
99             unless blessed($class) && $class->isa('mop::class');
100              
101 14         65 $class->make_class_abstract;
102             }
103              
104             sub overload {
105 11     14 1 21 my ($method, $operator) = @_;
106              
107 11 50 33     155 die "overload trait is only valid on methods"
108             unless blessed($method) && $method->isa('mop::method');
109              
110 11         37 my $method_name = $method->name;
111              
112             # NOTE:
113             # This installs the methods into the package
114             # directly, rather than going through the
115             # mop. This is because overload methods
116             # (with their weird names) should probably
117             # not show up in the list of methods and such.
118              
119             overload::OVERLOAD(
120             $method->associated_meta->name,
121             $operator,
122             sub {
123 16     19   2811 my $self = shift;
124 16         97 $self->$method_name(@_)
125             },
126 11         41 fallback => 1
127             );
128             }
129              
130             sub weak_ref {
131 5     5 1 11 my ($attr) = @_;
132              
133 5 50 33     64 die "weak_ref trait is only valid on attributes"
134             unless blessed($attr) && $attr->isa('mop::attribute');
135              
136 5         19 weaken(my $weak_attr = $attr);
137             $attr->bind('after:STORE_DATA' => sub {
138 5     5   13 my (undef, $instance) = @_;
139 5         22 $weak_attr->weaken_data_in_slot_for($instance);
140 5         37 });
141             }
142              
143             sub lazy {
144 9     9 1 19 my ($attr) = @_;
145              
146 9 50 33     119 die "lazy trait is only valid on attributes"
147             unless blessed($attr) && $attr->isa('mop::attribute');
148              
149 9         42 my $default = $attr->clear_default;
150 9         52 weaken(my $weak_attr = $attr);
151             $attr->bind('before:FETCH_DATA' => sub {
152 32     32   53 my (undef, $instance) = @_;
153 32 100       89 if ( !$weak_attr->has_data_in_slot_for($instance) ) {
154 18         29 $weak_attr->store_data_in_slot_for($instance, do {
155 18         25 local $_ = $instance;
156 18 100       190 ref($default) ? $default->() : $default
157             });
158             }
159 9         81 });
160             }
161              
162             sub extending_non_mop {
163 5     5 1 14 my ($class, $constructor_name) = @_;
164              
165 5 50 33     67 die "extending_non_mop trait is only valid on classes"
166             unless blessed($class) && $class->isa('mop::class');
167              
168 5   100     30 $constructor_name //= 'new';
169 5         23 my $super_constructor = join '::' => $class->superclass, $constructor_name;
170              
171             $class->add_method(
172             $class->method_class->new(
173             name => $constructor_name,
174             body => sub {
175 4     4   51 my $class = shift;
176 4         34 my $self = $class->$super_constructor( @_ );
177 4         44 mop::internals::util::register_object( $self );
178              
179             my %attributes = map {
180 8 100       33 if (my $m = mop::meta($_)) {
  4         27  
181 4         6 %{ $m->attribute_map }
  4         24  
182             }
183             else {
184             ()
185 4         9 }
186 4         8 } reverse @{ mro::get_linear_isa($class) };
187              
188 4         17 foreach my $attr (values %attributes) {
189 6         31 $attr->store_default_in_slot_for( $self );
190             }
191              
192 4         29 mop::internals::util::buildall($self, @_);
193              
194 4         20 $self;
195             }
196             )
197 5         26 );
198             }
199              
200             sub repr {
201 9     9 1 15 my ($class, $instance) = @_;
202              
203 9 50 33     74 die "repr trait is only valid on classes"
204             unless blessed($class) && $class->isa('mop::class');
205              
206 9         14 my $generator;
207 9 100 100     47 if (ref $instance && ref $instance eq 'CODE') {
    100          
208 1         4 $generator = $instance;
209             }
210             elsif (!ref $instance) {
211 7 100       50 if ($instance eq 'SCALAR') {
    100          
    100          
    100          
212 1     1   5 $generator = sub { \(my $anon) };
  1         4  
213             }
214             elsif ($instance eq 'ARRAY') {
215 1     1   6 $generator = sub { [] };
  1         4  
216             }
217             elsif ($instance eq 'HASH') {
218 3     9   13 $generator = sub { {} };
  9         31  
219             }
220             elsif ($instance eq 'GLOB') {
221 1     1   5 $generator = sub { select select my $fh; %{*$fh} = (); $fh };
  1         17  
  1         2  
  1         4  
  1         5  
222             }
223             else {
224 1         17 die "unknown instance generator type $instance";
225             }
226             }
227             else {
228 1         16 die "unknown instance generator $instance";
229             }
230              
231 7         58 $class->set_instance_generator($generator);
232             }
233              
234             1;
235              
236             __END__