File Coverage

blib/lib/Specio/OO.pm
Criterion Covered Total %
statement 146 153 95.4
branch 34 36 94.4
condition 5 6 83.3
subroutine 23 26 88.4
pod 0 1 0.0
total 208 222 93.6


line stmt bran cond sub pod time code
1             package Specio::OO;
2              
3 32     32   237 use strict;
  32         167  
  32         1254  
4 32     32   139 use warnings;
  32         50  
  32         1559  
5              
6 32     32   162 use Carp qw( confess );
  32         44  
  32         1977  
7 32     32   164 use List::Util 1.33 qw( all );
  32         2423  
  32         2262  
8 32     32   15065 use MRO::Compat;
  32         61959  
  32         1115  
9 32     32   6506 use Role::Tiny;
  32         65959  
  32         222  
10 32     32   8478 use Scalar::Util qw( weaken );
  32         66  
  32         2007  
11 32     32   9174 use Specio qw( _clone );
  32         75  
  32         1686  
12 32     32   4544 use Specio::Helpers qw( perlstring );
  32         77  
  32         1684  
13 32     32   14778 use Specio::PartialDump qw( partial_dump );
  32         79  
  32         2024  
14 32     32   13875 use Specio::TypeChecks;
  32         85  
  32         2056  
15              
16             our $VERSION = '0.53';
17              
18 32     32   215 use Exporter qw( import );
  32         54  
  32         8148  
19              
20             ## no critic (Modules::ProhibitAutomaticExportation)
21             our @EXPORT = qw(
22             clone
23             _ooify
24             );
25             ## use critic
26              
27             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
28             sub _ooify {
29 225     225   507 my $class = shift;
30              
31 225         792 my $attrs = $class->_attrs;
32 225         446 for my $name ( sort keys %{$attrs} ) {
  225         1916  
33 2486         4591 my $attr = $attrs->{$name};
34              
35 2486         6964 _inline_reader( $class, $name, $attr );
36 2486         6120 _inline_predicate( $class, $name, $attr );
37             }
38              
39 225         1076 _inline_constructor($class);
40             }
41             ## use critic
42              
43             sub _inline_reader {
44 2486     2486   3465 my $class = shift;
45 2486         3195 my $name = shift;
46 2486         3130 my $attr = shift;
47              
48 2486         3087 my $reader;
49 2486 100 66     9495 if ( $attr->{lazy} && ( my $builder = $attr->{builder} ) ) {
50 1078         1720 my $source = <<'EOF';
51             sub {
52             unless ( exists $_[0]->{%s} ) {
53             $_[0]->{%s} = $_[0]->%s;
54             Scalar::Util::weaken( $_[0]->{%s} ) if %s && ref $_[0]->{%s};
55             }
56             $_[0]->{%s};
57             }
58             EOF
59             $reader = sprintf(
60             $source,
61             $name,
62             $name,
63             $builder,
64             $name,
65 1078 50       5777 ( $attr->{weak_ref} ? 1 : 0 ),
66             $name,
67             $name,
68             );
69             }
70             else {
71 1408         2406 $reader = sprintf( 'sub { $_[0]->{%s} }', $name );
72             }
73              
74             {
75             ## no critic (TestingAndDebugging::ProhibitNoStrict)
76 32     32   245 no strict 'refs';
  32         77  
  32         4926  
  2486         3209  
77 2486         6214 *{ $class . '::' . $name } = _eval_or_die(
  2486         18996  
78             $reader, $class . '->' . $name,
79             );
80             }
81             }
82              
83             sub _inline_predicate {
84 2486     2486   3935 my $class = shift;
85 2486         3173 my $name = shift;
86 2486         3088 my $attr = shift;
87              
88 2486 100       7839 return unless $attr->{predicate};
89              
90 632         1067 my $predicate = "sub { exists \$_[0]->{$name} }";
91              
92             {
93             ## no critic (TestingAndDebugging::ProhibitNoStrict)
94 32     32   190 no strict 'refs';
  32         66  
  32         6231  
  632         917  
95 632         5759 *{ $class . '::' . $attr->{predicate} } = _eval_or_die(
96             $predicate, $class . '->' . $attr->{predicate},
97 632         1999 );
98             }
99             }
100              
101             my @RolesWithBUILD = qw( Specio::Constraint::Role::Interface );
102              
103             # This is an optimization to avoid calling this many times over:
104             #
105             # Specio::TypeChecks->can( 'is_' . $attr->{isa} )
106             my %TypeChecks;
107              
108             BEGIN {
109 32     32   288 for my $sub (@Specio::TypeChecks::EXPORT_OK) {
110 256 100       3495 my ($type) = $sub =~ /^is_(.+)$/
111             or next;
112 192         951 $TypeChecks{$type} = Specio::TypeChecks->can($sub);
113             }
114             }
115              
116             sub _inline_constructor {
117 225     225   406 my $class = shift;
118              
119 225         374 my @build_subs;
120 225         346 for my $parent ( @{ mro::get_linear_isa($class) } ) {
  225         1668  
121             {
122             ## no critic (TestingAndDebugging::ProhibitNoStrict)
123 32     32   199 no strict 'refs';
  32         70  
  32         17802  
  225         385  
124             push @build_subs, $parent . '::BUILD'
125 225 100       312 if defined &{ $parent . '::BUILD' };
  225         1481  
126             }
127             }
128              
129             # This is all a hack to avoid needing Class::Method::Modifiers to add a
130             # BUILD from a role. We can't just call the method in the role "BUILD" or
131             # it will be shadowed by a class's BUILD. So we give it a wacky unique
132             # name. We need to explicitly know which roles have a _X_BUILD method
133             # because Role::Tiny doesn't provide a way to list all the roles applied
134             # to a class.
135 225         945 for my $role (@RolesWithBUILD) {
136 225 100       1222 if ( Role::Tiny::does_role( $class, $role ) ) {
137 129         3085 ( my $build_name = $role ) =~ s/::/_/g;
138 129         322 $build_name = q{_} . $build_name . '_BUILD';
139 129         491 push @build_subs, $role . '::' . $build_name;
140             }
141             }
142              
143 225         1798 my $constructor = <<'EOF';
144             sub {
145             my $class = shift;
146              
147             my %p = do {
148             if ( @_ == 1 ) {
149             if ( ref $_[0] eq 'HASH' ) {
150             %{ shift() };
151             }
152             else {
153             Specio::OO::_constructor_confess(
154             Specio::OO::_bad_args_message( $class, @_ ) );
155             }
156             }
157             else {
158             Specio::OO::_constructor_confess(
159             Specio::OO::_bad_args_message( $class, @_ ) )
160             if @_ % 2;
161             @_;
162             }
163             };
164              
165             my $self = bless {}, $class;
166              
167             EOF
168              
169 225         774 my $attrs = $class->_attrs;
170 225         388 for my $name ( sort keys %{$attrs} ) {
  225         1816  
171 2486         4953 my $attr = $attrs->{$name};
172 2486 100       4467 my $key_name = defined $attr->{init_arg} ? $attr->{init_arg} : $name;
173              
174 2486 100       4460 if ( $attr->{required} ) {
175 522         1024 $constructor .= <<"EOF";
176             Specio::OO::_constructor_confess(
177             "$class->new requires a $key_name argument.")
178             unless exists \$p{$key_name};
179             EOF
180             }
181              
182 2486 100 100     6304 if ( $attr->{builder} && !$attr->{lazy} ) {
183 169         320 my $builder = $attr->{builder};
184 169         472 $constructor .= <<"EOF";
185             \$p{$key_name} = $class->$builder unless exists \$p{$key_name};
186             EOF
187             }
188              
189 2486 100       4296 if ( $attr->{isa} ) {
190 1948         2227 my $validator;
191 1948 100       4176 if ( $TypeChecks{ $attr->{isa} } ) {
192             $validator
193             = 'Specio::TypeChecks::is_'
194             . $attr->{isa}
195 1751         2916 . "( \$p{$key_name} )";
196             }
197             else {
198 197         1275 my $quoted_class = perlstring( $attr->{isa} );
199 197         449 $validator
200             = "Specio::TypeChecks::isa_class( \$p{$key_name}, $quoted_class )";
201             }
202              
203 1948         4732 $constructor .= <<"EOF";
204             if ( exists \$p{$key_name} && !$validator ) {
205             Carp::confess(
206             Specio::OO::_bad_value_message(
207             "The value you provided to $class->new for $key_name is not a valid $attr->{isa}.",
208             \$p{$key_name},
209             )
210             );
211             }
212             EOF
213             }
214              
215 2486 100       4354 if ( $attr->{does} ) {
216 252         1363 my $quoted_role = perlstring( $attr->{does} );
217 252         938 $constructor .= <<"EOF";
218             if ( exists \$p{$key_name} && !Specio::TypeChecks::does_role( \$p{$key_name}, $quoted_role ) ) {
219             Carp::confess(
220             Specio::OO::_bad_value_message(
221             "The value you provided to $class->new for $key_name does not do the $attr->{does} role.",
222             \$p{$key_name},
223             )
224             );
225             }
226             EOF
227             }
228              
229 2486 100       4014 if ( $attr->{weak_ref} ) {
230 32         105 $constructor .= " Scalar::Util::weaken( \$p{$key_name} );\n";
231             }
232              
233             $constructor
234 2486         3759 .= " \$self->{$name} = \$p{$key_name} if exists \$p{$key_name};\n";
235              
236 2486         3933 $constructor .= "\n";
237             }
238              
239 225         897 $constructor .= ' $self->' . $_ . "(\\%p);\n" for @build_subs;
240 225         384 $constructor .= <<'EOF';
241              
242             return $self;
243             }
244             EOF
245              
246             {
247             ## no critic (TestingAndDebugging::ProhibitNoStrict)
248 32     32   238 no strict 'refs';
  32         86  
  32         21691  
  225         311  
249 225         739 *{ $class . '::new' } = _eval_or_die(
  225         2404  
250             $constructor, $class . '->new',
251             );
252             }
253             }
254              
255             # This used to be done with Eval::Closure but that added a lot of unneeded
256             # overhead. We're never actually eval'ing a closure, just plain source, so
257             # doing it by hand is a worthwhile optimization.
258             sub _eval_or_die {
259 3343     3343   5310 local $@ = undef;
260             ## no critic (Variables::RequireInitializationForLocalVars)
261             # $SIG{__DIE__} = undef causes warnings with 5.8.x
262 3343         11246 local $SIG{__DIE__};
263             ## no critic (BuiltinFunctions::ProhibitStringyEval)
264 3343         805645 my $sub = eval <<"EOF";
265             #line 1 "$_[1]"
266             $_[0];
267             EOF
268 3343         47491 my $e = $@;
269 3343 50       8981 die $e if $e;
270 3343         12410 return $sub;
271             }
272              
273             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
274             sub _constructor_confess {
275 0     0   0 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
276 0         0 confess shift;
277             }
278              
279             sub _bad_args_message {
280 0     0   0 my $class = shift;
281              
282             return
283 0         0 "$class->new requires either a hashref or hash as arguments. You passed "
284             . partial_dump(@_);
285             }
286              
287             sub _bad_value_message {
288 0     0   0 my $message = shift;
289 0         0 my $value = shift;
290              
291 0         0 return $message . ' You passed ' . partial_dump($value);
292             }
293             ## use critic
294              
295             my %BuiltinTypes = map { $_ => 1 } qw(
296             SCALAR
297             ARRAY
298             HASH
299             CODE
300             REF
301             GLOB
302             LVALUE
303             FORMAT
304             IO
305             VSTRING
306             Regexp
307             );
308              
309             sub clone {
310 18684     18684 0 26250 my $self = shift;
311              
312             # Attributes which provide a clone method are cloned by calling that
313             # method on the _clone_ (not the original). This is primarily to allow us
314             # to clone the coercions contained by a type in a way that doesn't lead to
315             # circular clone (type clones coercions which in turn need to clone their
316             # to/from types which in turn ...).
317 18684         54581 my $attrs = $self->_attrs;
318 9341         25381 my %special = map { $_ => $attrs->{$_}{clone} }
319 18684         25353 grep { $attrs->{$_}{clone} } keys %{$attrs};
  169180         268777  
  18684         60664  
320              
321 18684         35917 my $new;
322 18684         23370 for my $key ( keys %{$self} ) {
  18684         49977  
323 101006         160793 my $value = $self->{$key};
324              
325 101006 100       170947 if ( $special{$key} ) {
326 9341         17968 $new->{$key} = $value;
327 9341         15770 next;
328             }
329              
330             # This is a weird hacky way of trying to avoid calling
331             # Scalar::Util::blessed, which showed up as a hotspot in profiling of
332             # loading DateTime. That's because we call ->clone a _lot_ (it's
333             # called every time a type is exported).
334 91665         132989 my $ref = ref $value;
335             $new->{$key}
336             = !$ref ? $value
337             : $ref eq 'CODE' ? $value
338 91665 100       243244 : $BuiltinTypes{$ref} ? _clone($value)
    100          
    100          
339             : $value->clone;
340             }
341              
342 18684         42309 bless $new, ( ref $self );
343              
344 18684         31906 for my $key ( keys %special ) {
345 9341         13956 my $method = $special{$key};
346 9341         25368 $new->{$key} = $new->$method;
347             }
348              
349 18684         51886 return $new;
350             }
351              
352             1;
353              
354             # ABSTRACT: A painfully poor reimplementation of Moo(se)
355              
356             __END__
357              
358             =pod
359              
360             =encoding UTF-8
361              
362             =head1 NAME
363              
364             Specio::OO - A painfully poor reimplementation of Moo(se)
365              
366             =head1 VERSION
367              
368             version 0.53
369              
370             =head1 DESCRIPTION
371              
372             Specio can't depend on Moo or Moose, so this module provides a terrible
373             reimplementation of a small slice of their features.
374              
375             =for Pod::Coverage .*
376              
377             =head1 SUPPORT
378              
379             Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
380              
381             =head1 SOURCE
382              
383             The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
384              
385             =head1 AUTHOR
386              
387             Dave Rolsky <autarch@urth.org>
388              
389             =head1 COPYRIGHT AND LICENSE
390              
391             This software is Copyright (c) 2012 - 2025 by Dave Rolsky.
392              
393             This is free software, licensed under:
394              
395             The Artistic License 2.0 (GPL Compatible)
396              
397             The full text of the license can be found in the
398             F<LICENSE> file included with this distribution.
399              
400             =cut