File Coverage

blib/lib/mop/internals/util.pm
Criterion Covered Total %
statement 217 217 100.0
branch 59 68 86.7
condition 13 18 72.2
subroutine 28 28 100.0
pod 0 16 0.0
total 317 347 91.3


line stmt bran cond sub pod time code
1             package mop::internals::util;
2 143     143   1445 use v5.16;
  143         1659  
  143         5170  
3 143     143   786 use warnings;
  143         269  
  143         3979  
4              
5 143     143   173188 use Hash::Util::FieldHash;
  143         206365  
  143         7230  
6 143     143   960 use mro ();
  143         262  
  143         2154  
7 143     143   682 use Scalar::Util ();
  143         251  
  143         19820  
8              
9             our $VERSION = '0.03';
10             our $AUTHORITY = 'cpan:STEVAN';
11              
12             # XXX all of this OVERRIDDEN stuff really needs to go, ideally replaced by
13             # lexical exports
14             my %OVERRIDDEN;
15              
16             sub install_sub {
17 2184     2184 0 3746 my ($to, $from, $sub) = @_;
18 143     143   785 no strict 'refs';
  143         292  
  143         21704  
19 2184 50       2657 if (*{ "${to}::${sub}" }) {
  2184         19672  
20 2184   100     2249 push @{ $OVERRIDDEN{$to}{$sub} //= [] }, \&{ "${to}::${sub}" };
  2184         10018  
  2184         7903  
21             }
22 143     143   849 no warnings 'redefine';
  143         269  
  143         16722  
23 2184         2654 *{ $to . '::' . $sub } = \&{ "${from}::${sub}" };
  2184         10739  
  2184         7711  
24             }
25              
26             sub uninstall_sub {
27 78     78 0 102 my ($pkg, $sub) = @_;
28 143     143   1034 no strict 'refs';
  143         326  
  143         165617  
29 78         143 delete ${ $pkg . '::' }{$sub};
  78         381  
30 78 50 50     81 if (my $prev = pop @{ $OVERRIDDEN{$pkg}{$sub} // [] }) {
  78         295  
31 78         74 *{ $pkg . '::' . $sub } = $prev;
  78         2717  
32             }
33             }
34              
35             sub init_attribute_storage (\%) {
36 3058     3058 0 9386 &Hash::Util::FieldHash::fieldhash( $_[0] )
37             }
38              
39             sub register_object {
40 23176     23176 0 203392 Hash::Util::FieldHash::register( $_[0] )
41             }
42              
43             {
44             my %NONMOP_CLASSES;
45              
46             sub mark_nonmop_class {
47 7     7 0 35 my ($class) = @_;
48 7         28 $NONMOP_CLASSES{$class} = 1;
49             }
50              
51             sub is_nonmop_class {
52 495     495 0 859 my ($class) = @_;
53 495         2096 $NONMOP_CLASSES{$class};
54             }
55             }
56              
57             sub install_meta {
58 495     495 0 1057 my ($meta) = @_;
59              
60 495         1638 my $name = $meta->name;
61              
62 495 50       1452 die "The metaclass for $name has already been created"
63             if mop::meta($name);
64              
65 495 100       2935 die "$name has already been used as a non-mop class. "
66             . "Does your code have a circular dependency?"
67             if is_nonmop_class($name);
68              
69 494         4651 set_meta($name, $meta);
70              
71 494   100     14189 $INC{ ($name =~ s{::}{/}gr) . '.pm' } //= '(mop)'; #'syntax highlighting sucks
72             }
73              
74             sub apply_all_roles {
75 217     217 0 550 my ($to, @roles) = @_;
76              
77 217         795 unapply_all_roles($to);
78              
79 217         938 my $composite = create_composite_role(@roles);
80              
81 215         1877 $to->fire('before:CONSUME' => $composite);
82 215         716 $composite->fire('before:COMPOSE' => $to);
83              
84 215         737 foreach my $attribute ($composite->attributes) {
85 1163 100 66     3074 die 'Attribute conflict ' . $attribute->name . ' when composing ' . $composite->name . ' into ' . $to->name
86             if $to->has_attribute( $attribute->name )
87             && $to->get_attribute( $attribute->name )->conflicts_with( $attribute );
88 1161         3938 $to->add_attribute( $attribute->clone(associated_meta => $to) );
89             }
90              
91 213         939 foreach my $method ($composite->methods) {
92 4634 100       11992 if (my $existing_method = $to->get_method($method->name)) {
93 152         808 mop::apply_metaclass($existing_method, $method);
94             }
95             else {
96 4482         11054 $to->add_method($method->clone(associated_meta => $to));
97             }
98             }
99              
100             # merge required methods ...
101 213         1234 for my $conflict ($composite->required_methods) {
102 15 100       59 if (my $method = $to->get_method($conflict)) {
103 12         128 my @conflicting_methods =
104 8         24 grep { $_->name eq $conflict }
105 5         18 map { $_->methods }
106 5         11 @{ $composite->roles };
107 5         16 for my $conflicting_method (@conflicting_methods) {
108 6         18 mop::apply_metaclass($method, $conflicting_method);
109             }
110             }
111             else {
112 10         42 $to->add_required_method($conflict);
113             }
114             }
115              
116 213         1091 $composite->fire('after:COMPOSE' => $to);
117 213         752 $to->fire('after:CONSUME' => $composite);
118             }
119              
120             sub unapply_all_roles {
121 217     217 0 422 my ($meta) = @_;
122              
123 217         1270 for my $attr ($meta->attributes) {
124 434 100       1433 $meta->remove_attribute($attr->name)
125             unless $attr->locally_defined;
126             }
127              
128 217         1575 for my $method ($meta->methods) {
129 1316 100       3430 $meta->remove_method($method->name)
130             unless $method->locally_defined;
131             }
132              
133             # XXX this is wrong, it will also remove required methods that were
134             # defined in the class directly
135             $meta->remove_required_method($_)
136 217         1549 for $meta->required_methods;
137             }
138              
139             # this shouldn't be used, generally. the only case where this is necessary is
140             # when we have a class which doesn't use the mop inheriting from a class which
141             # does. in that case, we need to inflate a basic metaclass for that class in
142             # order to be able to instantiate new instances via new_instance. see
143             # mop::object::new.
144             sub find_or_inflate_meta {
145 1457     1457 0 2153 my ($class) = @_;
146              
147 1457 100       15258 if (my $meta = mop::meta($class)) {
148 1449         7915 return $meta;
149             }
150             else {
151 8         35 return inflate_meta($class);
152             }
153             }
154              
155             sub inflate_meta {
156 8     8 0 21 my ($class) = @_;
157              
158 8         16 my $name = $class;
159 143     143   902 my $version = do { no strict 'refs'; ${ *{ $class . '::VERSION' }{SCALAR} } };
  143         282  
  143         8610  
  8         14  
  8         10  
  8         15  
  8         57  
160 143     143   724 my $authority = do { no strict 'refs'; ${ *{ $class . '::AUTHORITY' }{SCALAR} } };
  143         271  
  143         7456  
  8         17  
  8         11  
  8         10  
  8         489  
161 143     143   673 my $isa = do { no strict 'refs'; *{ $class . '::ISA' }{ARRAY} };
  143         262  
  143         16544  
  8         21  
  8         13  
  8         32  
162              
163 8 50       32 die "Multiple inheritance is not supported in mop classes"
164             if @$isa > 1;
165              
166 8         57 my $new_meta = mop::class->new(
167             name => $name,
168             version => $version,
169             authority => $authority,
170             superclass => $isa->[0],
171             );
172              
173 143     143   756 for my $method (do { no strict 'refs'; keys %{ $class . '::' } }) {
  143         2597  
  143         271344  
  8         15  
  8         11  
  8         43  
174 48 100       340 next unless $class->can($method);
175 24         140 $new_meta->add_method(
176             mop::method->new(
177             name => $method,
178             body => $class->can($method),
179             )
180             );
181             }
182              
183 8         43 return $new_meta;
184             }
185              
186             sub fix_metaclass_compatibility {
187 447     447 0 742 my ($meta, $super) = @_;
188              
189 447   66     2530 my $meta_name = Scalar::Util::blessed($meta) // $meta;
190 447 50       1637 return $meta_name if !defined $super; # non-mop inheritance
191              
192 447   33     1848 my $super_name = Scalar::Util::blessed($super) // $super;
193              
194 447 100       4437 return $meta_name if $meta_name->isa($super_name);
195 45 100       309 return $super_name if $super_name->isa($meta_name);
196              
197 17         66 my $rebased_meta_name = rebase_metaclasses($meta_name, $super_name);
198 17 100       97 return $rebased_meta_name if $rebased_meta_name;
199              
200 4 100       98 my $meta_desc = Scalar::Util::blessed($meta)
201             ? $meta->name . " ($meta_name)"
202             : $meta_name;
203 4 50       39 my $super_desc = Scalar::Util::blessed($super)
204             ? $super->name . " ($super_name)"
205             : $super_name;
206 4         111 die "Can't fix metaclass compatibility between $meta_desc and $super_desc";
207             }
208              
209             sub rebase_metaclasses {
210 17     17 0 3112 my ($meta_name, $super_name) = @_;
211              
212 17         48 my $common_base = find_common_base($meta_name, $super_name);
213 17 50       58 return unless $common_base;
214              
215 17         31 my @meta_isa = @{ mro::get_linear_isa($meta_name) };
  17         232  
216 17         89 pop @meta_isa until $meta_isa[-1] eq $common_base;
217 17         28 pop @meta_isa;
218 17         46 @meta_isa = reverse map { mop::meta($_) } @meta_isa;
  21         70  
219              
220 17         31 my @super_isa = @{ mro::get_linear_isa($super_name) };
  17         87  
221 17         90 pop @super_isa until $super_isa[-1] eq $common_base;
222 17         24 pop @super_isa;
223 17         36 @super_isa = reverse map { mop::meta($_) } @super_isa;
  21         63  
224              
225             # XXX i just haven't thought through exactly what this would mean - this
226             # restriction may be able to be lifted in the future
227 17 50       40 return if grep { $_->is_abstract } @meta_isa, @super_isa;
  42         120  
228              
229 17         44 my %super_method_overrides = map { %{ $_->method_map } } @super_isa;
  21         39  
  21         64  
230 17         37 my %super_attribute_overrides = map { %{ $_->attribute_map } } @super_isa;
  21         29  
  21         67  
231              
232 17         34 my $current = $super_name;
233 17         45 for my $class (@meta_isa) {
234 21         198 return if grep {
235 21 100       74 $super_method_overrides{$_->name}
236             } $class->methods;
237              
238 1         3 return if grep {
239 17 50       64 $super_attribute_overrides{$_->name}
240             } $class->attributes;
241              
242 17         55 my $class_name = $class->name;
243 17         70 my $rebased = "mop::class::rebased::${class_name}::for::${current}";
244 17 100       49 if (!mop::meta($rebased)) {
245 11         54 my $clone = $class->clone(
246             name => $rebased,
247             superclass => $current,
248             );
249 11         41 $clone->FINALIZE;
250             }
251 17         54 $current = $rebased;
252             }
253              
254 13         62 return $current;
255             }
256              
257             sub find_common_base {
258 470     470 0 888 my ($meta_name, $super_name) = @_;
259              
260 1014         3056 my %meta_ancestors =
261 470         932 map { $_ => 1 } @{ mro::get_linear_isa($meta_name) };
  470         2011  
262              
263 470         1131 for my $super_ancestor (@{ mro::get_linear_isa($super_name) }) {
  470         1842  
264 540 100       4042 return $super_ancestor if $meta_ancestors{$super_ancestor};
265             }
266              
267 1         4 return;
268             }
269              
270             sub create_composite_role {
271 217     217 0 542 my (@roles) = @_;
272              
273 217 100       522 @roles = map { ref($_) ? $_ : mop::meta($_) } @roles;
  251         1398  
274              
275 217 100       1176 return $roles[0] if @roles == 1;
276              
277 59         204 my $name = 'mop::role::COMPOSITE::OF::'
278 25         67 . (join '::' => map { $_->name } @roles);
279 25 100       100 return mop::meta($name) if mop::meta($name);
280              
281 22         137 my $composite = mop::role->new(
282             name => $name,
283             roles => [ @roles ],
284             );
285              
286             $composite->fire('before:CONSUME' => $_)
287 22         161 for @roles;
288             $_->fire('before:COMPOSE' => $composite)
289 22         101 for @roles;
290              
291             {
292 22         40 my %attributes;
  22         53  
293 22         53 for my $role (@roles) {
294 53         159 for my $attribute ($role->attributes) {
295 9         31 my $name = $attribute->name;
296 9         21 my $seen = $attributes{$name};
297 9 100 100     58 die "Attribute conflict $name when composing "
298             . $seen->associated_meta->name . " with " . $role->name
299             if $seen && $seen->conflicts_with($attribute);
300 7         17 $attributes{$name} = $attribute;
301 7         37 $composite->add_attribute(
302             $attribute->clone(associated_meta => $composite)
303             );
304             }
305             }
306             }
307              
308             {
309 20         49 my %methods;
  20         33  
310             my %conflicts;
311 20         42 for my $role (@roles) {
312 49         255 for my $method ($role->methods) {
313 38         112 my $name = $method->name;
314 38 100       165 if ($conflicts{$name}) {
    100          
315 6         20 next;
316             }
317             elsif ($methods{$name}) {
318 11 100       49 next unless $methods{$name}->conflicts_with($method);
319 9         56 $conflicts{$name} = delete $methods{$name};
320             }
321             else {
322 21         122 $methods{$name} = $method;
323             }
324             }
325             }
326 20         65 for my $name (keys %methods) {
327 12         59 $composite->add_method(
328             $methods{$name}->clone(associated_meta => $composite)
329             );
330             }
331 20         77 for my $requirement (keys %conflicts) {
332 9         105 $composite->add_required_method($requirement);
333             }
334             }
335              
336 20         48 for my $role (@roles) {
337 49         201 for my $requirement ($role->required_methods) {
338 2 100       9 $composite->add_required_method($requirement)
339             unless $composite->has_method($requirement);
340             }
341             }
342              
343             $_->fire('after:COMPOSE' => $composite)
344 20         99 for @roles;
345             $composite->fire('after:CONSUME' => $_)
346 20         106 for @roles;
347              
348 20         60 return $composite;
349             }
350              
351             sub buildall {
352 1577     1577 0 2833 my ($instance, @args) = @_;
353              
354 1577         2341 foreach my $class (reverse @{ mro::get_linear_isa(ref $instance) }) {
  1577         7602  
355 3287 100       10393 if (my $m = mop::meta($class)) {
356 3283 100       9275 $m->get_method('BUILD')->execute($instance, [ @args ])
357             if $m->has_method('BUILD');
358             }
359             }
360             }
361              
362             1;
363              
364             __END__