File Coverage

blib/lib/Specio/Constraint/Role/Interface.pm
Criterion Covered Total %
statement 229 244 93.8
branch 39 48 81.2
condition 15 21 71.4
subroutine 63 74 85.1
pod 0 28 0.0
total 346 415 83.3


line stmt bran cond sub pod time code
1             package Specio::Constraint::Role::Interface;
2              
3 29     29   6904 use strict;
  29         70  
  29         872  
4 29     29   164 use warnings;
  29         53  
  29         1295  
5              
6             our $VERSION = '0.47';
7              
8 29     29   154 use Carp qw( confess );
  29         55  
  29         1463  
9 29     29   8119 use Eval::Closure qw( eval_closure );
  29         21516  
  29         1632  
10 29     29   203 use List::Util 1.33 qw( all any first );
  29         859  
  29         1975  
11 29     29   11925 use Specio::Exception;
  29         71  
  29         1102  
12 29     29   214 use Specio::PartialDump qw( partial_dump );
  29         84  
  29         1430  
13 29     29   173 use Specio::TypeChecks qw( is_CodeRef );
  29         64  
  29         1515  
14              
15 29     29   167 use Role::Tiny 1.003003;
  29         598  
  29         204  
16              
17 29     29   13037 use Specio::Role::Inlinable;
  29         66  
  29         1844  
18             with 'Specio::Role::Inlinable';
19              
20             use overload(
21             q{""} => '_stringify',
22             '&{}' => '_subification',
23 4030     4030   15861 'bool' => sub {1},
24 29         224 'eq' => 'is_same_type_as',
25 29     29   206 );
  29         55  
26              
27             {
28             ## no critic (Subroutines::ProtectPrivateSubs)
29             my $role_attrs = Specio::Role::Inlinable::_attrs();
30             ## use critic
31              
32             my $attrs = {
33             %{$role_attrs},
34             name => {
35             isa => 'Str',
36             predicate => '_has_name',
37             },
38             parent => {
39             does => 'Specio::Constraint::Role::Interface',
40             predicate => '_has_parent',
41             },
42             _constraint => {
43             isa => 'CodeRef',
44             init_arg => 'constraint',
45             predicate => '_has_constraint',
46             },
47             _optimized_constraint => {
48             isa => 'CodeRef',
49             init_arg => undef,
50             lazy => 1,
51             builder => '_build_optimized_constraint',
52             },
53             _ancestors => {
54             isa => 'ArrayRef',
55             init_arg => undef,
56             lazy => 1,
57             builder => '_build_ancestors',
58             },
59             _message_generator => {
60             isa => 'CodeRef',
61             init_arg => undef,
62             },
63             _coercions => {
64             builder => '_build_coercions',
65             clone => '_clone_coercions',
66             },
67             _subification => {
68             init_arg => undef,
69             lazy => 1,
70             builder => '_build_subification',
71             },
72              
73             # Because types are cloned on import, we can't directly compare type
74             # objects. Because type names can be reused between packages (no global
75             # registry) we can't compare types based on name either.
76             _signature => {
77             isa => 'Str',
78             init_arg => undef,
79             lazy => 1,
80             builder => '_build_signature',
81             },
82             };
83              
84             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
85             sub _attrs {
86 8867     8867   22466 return $attrs;
87             }
88             }
89              
90             my $NullConstraint = sub {1};
91              
92             # See Specio::OO to see how this is used.
93              
94             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
95             sub _Specio_Constraint_Role_Interface_BUILD {
96 908     908   15455 my $self = shift;
97 908         1496 my $p = shift;
98              
99 908 100 100     2482 unless ( $self->_has_constraint || $self->_has_inline_generator ) {
100 26         267 $self->{_constraint} = $NullConstraint;
101             }
102              
103             die
104 908 50 66     8689 'A type constraint should have either a constraint or inline_generator parameter, not both'
105             if $self->_has_constraint && $self->_has_inline_generator;
106              
107             $self->{_message_generator}
108 908         5808 = $self->_wrap_message_generator( $p->{message_generator} );
109              
110 908         2704 return;
111             }
112             ## use critic
113              
114             sub _wrap_message_generator {
115 883     883   1558 my $self = shift;
116 883         2065 my $generator = shift;
117              
118 883 50       1962 unless ( defined $generator ) {
119             $generator = sub {
120 2359     2359   4674 my $description = shift;
121 2359         3754 my $value = shift;
122              
123 2359         9588 return "Validation failed for $description with value "
124             . partial_dump($value);
125 883         5020 };
126             }
127              
128 883         2832 my $d = $self->description;
129              
130 883     2359   7119 return sub { $generator->( $d, @_ ) };
  2359         165399  
131             }
132              
133 44     44 0 75 sub coercions { values %{ $_[0]->{_coercions} } }
  44         290  
134 0     0 0 0 sub coercion_from_type { $_[0]->{_coercions}{ $_[1] } }
135 22     22   108 sub _has_coercion_from_type { exists $_[0]->{_coercions}{ $_[1] } }
136 18     18   59 sub _add_coercion { $_[0]->{_coercions}{ $_[1] } = $_[2] }
137 63     63 0 3988 sub has_coercions { scalar keys %{ $_[0]->{_coercions} } }
  63         487  
138              
139             sub validate_or_die {
140 308     308 0 4340 my $self = shift;
141 308         570 my $value = shift;
142              
143 308 100       734 return if $self->value_is_valid($value);
144              
145 261         11355 Specio::Exception->throw(
146             message => $self->_message_generator->($value),
147             type => $self,
148             value => $value,
149             );
150             }
151              
152             sub value_is_valid {
153 3329     3329 0 32712 my $self = shift;
154 3329         5936 my $value = shift;
155              
156 3329         10937 return $self->_optimized_constraint->($value);
157             }
158              
159             sub _ancestors_and_self {
160 919     919   1572 my $self = shift;
161              
162 919         1319 return ( ( reverse @{ $self->_ancestors } ), $self );
  919         2761  
163             }
164              
165             sub is_a_type_of {
166 13     13 0 20 my $self = shift;
167 13         23 my $type = shift;
168              
169 40     40   486 return any { $_->_signature eq $type->_signature }
170 13         57 $self->_ancestors_and_self;
171             }
172              
173             sub is_same_type_as {
174 15     15 0 10167 my $self = shift;
175 15         39 my $type = shift;
176              
177 15         96 return $self->_signature eq $type->_signature;
178             }
179              
180             sub is_anon {
181 977     977 0 16592 my $self = shift;
182              
183 977         2837 return !$self->_has_name;
184             }
185              
186             sub has_real_constraint {
187 405     405 0 777 my $self = shift;
188              
189 405   100     1310 return ( $self->_has_constraint && $self->_constraint ne $NullConstraint )
190             || $self->_has_inline_generator;
191             }
192              
193             sub can_be_inlined {
194 1260     1260 0 2427 my $self = shift;
195              
196 1260 100       2754 return 1 if $self->_has_inline_generator;
197 81 100 66     509 return 0
198             if $self->_has_constraint && $self->_constraint ne $NullConstraint;
199              
200             # If this type is an empty subtype of an inlinable parent, then we can
201             # inline this type as well.
202 45 50 33     518 return 1 if $self->_has_parent && $self->parent->can_be_inlined;
203 0         0 return 0;
204             }
205              
206             sub _build_generated_inline_sub {
207 350     350   2284 my $self = shift;
208              
209 350         998 my $type = $self->_self_or_first_inlinable_ancestor;
210              
211 350         1300 my $source
212             = 'sub { ' . $type->_inline_generator->( $type, '$_[0]' ) . '}';
213              
214 350         1604 return eval_closure(
215             source => $source,
216             environment => $type->inline_environment,
217             description => 'inlined sub for ' . $self->description,
218             );
219             }
220              
221             sub _self_or_first_inlinable_ancestor {
222 818     818   1563 my $self = shift;
223              
224 843     843   6342 my $type = first { $_->_has_inline_generator }
225 818         3365 reverse $self->_ancestors_and_self;
226              
227             # This should never happen because ->can_be_inlined should always be
228             # checked before this builder is called.
229 818 50       4913 die 'Cannot generate an inline sub' unless $type;
230              
231 818         1662 return $type;
232             }
233              
234             sub _build_optimized_constraint {
235 109     109   1021 my $self = shift;
236              
237 109 100       366 if ( $self->can_be_inlined ) {
238 99         1038 return $self->_generated_inline_sub;
239             }
240             else {
241 10         192 return $self->_constraint_with_parents;
242             }
243             }
244              
245             sub _constraint_with_parents {
246 88     88   257 my $self = shift;
247              
248 88         238 my @constraints;
249 88         412 for my $type ( $self->_ancestors_and_self ) {
250 405 100       74926 next unless $type->has_real_constraint;
251              
252             # If a type can be inlined, we can use that and discard all of the
253             # ancestors we've seen so far, since we can assume that the inlined
254             # constraint does all of the ancestor checks in addition to its own.
255 397 100       5255 if ( $type->can_be_inlined ) {
256 383         2675 @constraints = $type->_generated_inline_sub;
257             }
258             else {
259 14         163 push @constraints, $type->_constraint;
260             }
261             }
262              
263 88 50       35299 return $NullConstraint unless @constraints;
264              
265             return sub {
266 1267     1267   8903 all { $_->( $_[0] ) } @constraints;
  1669         8510  
267 88         848 };
268             }
269              
270             # This is only used for identifying from types as part of coercions, but I
271             # want to leave open the possibility of using something other than
272             # _description in the future.
273             sub id {
274 22     22 0 74 my $self = shift;
275              
276 22         65 return $self->description;
277             }
278              
279             sub add_coercion {
280 18     18 0 93 my $self = shift;
281 18         32 my $coercion = shift;
282              
283 18         52 my $from_id = $coercion->from->id;
284              
285 18 50       161 confess "Cannot add two coercions fom the same type: $from_id"
286             if $self->_has_coercion_from_type($from_id);
287              
288 18         58 $self->_add_coercion( $from_id => $coercion );
289              
290 18         50 return;
291             }
292              
293             sub has_coercion_from_type {
294 4     4 0 9 my $self = shift;
295 4         6 my $type = shift;
296              
297 4         12 return $self->_has_coercion_from_type( $type->id );
298             }
299              
300             sub coerce_value {
301 16     16 0 2448 my $self = shift;
302 16         32 my $value = shift;
303              
304 16         43 for my $coercion ( $self->coercions ) {
305 22 100       131 next unless $coercion->from->value_is_valid($value);
306              
307 11         2570 return $coercion->coerce($value);
308             }
309              
310 5         98 return $value;
311             }
312              
313             sub can_inline_coercion {
314 5     5 0 12 my $self = shift;
315              
316 5     8   82 return all { $_->can_be_inlined } $self->coercions;
  8         39  
317             }
318              
319             sub can_inline_coercion_and_check {
320 11     11 0 24 my $self = shift;
321              
322 11     22   64 return all { $_->can_be_inlined } $self, $self->coercions;
  22         112  
323             }
324              
325             sub inline_coercion {
326 2     2 0 750 my $self = shift;
327 2         4 my $arg_name = shift;
328              
329 2 50       7 die 'Cannot inline coercion'
330             unless $self->can_inline_coercion;
331              
332 2         23 my $source = 'do { my $value = ' . $arg_name . ';';
333              
334 2         5 my ( $coerce, $env );
335 2         5 ( $coerce, $arg_name, $env ) = $self->_inline_coercion($arg_name);
336 2         13 $source .= $coerce . $arg_name . '};';
337              
338 2         8 return ( $source, $env );
339             }
340              
341             sub inline_coercion_and_check {
342 6     6 0 779 my $self = shift;
343 6         14 my $arg_name = shift;
344              
345 6 50       23 die 'Cannot inline coercion and check'
346             unless $self->can_inline_coercion_and_check;
347              
348 6         74 my $source = 'do { my $value = ' . $arg_name . ';';
349              
350 6         17 my ( $coerce, $env );
351 6         22 ( $coerce, $arg_name, $env ) = $self->_inline_coercion($arg_name);
352 6         24 my ( $assert, $assert_env ) = $self->inline_assert($arg_name);
353              
354 6         17 $source .= $coerce;
355 6         17 $source .= $assert;
356 6         14 $source .= $arg_name . '};';
357              
358 6         16 return ( $source, { %{$env}, %{$assert_env} } );
  6         16  
  6         44  
359             }
360              
361             sub _inline_coercion {
362 8     8   79 my $self = shift;
363 8         14 my $arg_name = shift;
364              
365 8 100       25 return ( q{}, $arg_name, {} ) unless $self->has_coercions;
366              
367 4         9 my %env;
368              
369 4         8 $arg_name = '$value';
370 4         10 my $source = $arg_name . ' = ';
371 4         13 for my $coercion ( $self->coercions ) {
372 9         51 $source
373             .= '('
374             . $coercion->from->inline_check($arg_name) . ') ? ('
375             . $coercion->inline_coercion($arg_name) . ') : ';
376              
377             %env = (
378             %env,
379 9         24 %{ $coercion->inline_environment },
380 9         69 %{ $coercion->from->inline_environment },
  9         50  
381             );
382             }
383 4         36 $source .= $arg_name . ';';
384              
385 4         18 return ( $source, $arg_name, \%env );
386             }
387              
388             {
389             my $counter = 1;
390              
391             sub inline_assert {
392 126     126 0 283 my $self = shift;
393              
394 126         375 my $type_var_name = '$_Specio_Constraint_Interface_type' . $counter;
395 126         656 my $message_generator_var_name
396             = '$_Specio_Constraint_Interface_message_generator' . $counter;
397             my %env = (
398             $type_var_name => \$self,
399             $message_generator_var_name => \( $self->_message_generator ),
400 126         657 %{ $self->inline_environment },
  126         787  
401             );
402              
403 126         1354 my $source = $self->inline_check( $_[0] );
404 126         580 $source .= ' or ';
405 126         546 $source .= $self->_inline_throw_exception(
406             $_[0],
407             $message_generator_var_name,
408             $type_var_name
409             );
410 126         271 $source .= ';';
411              
412 126         289 $counter++;
413              
414 126         653 return ( $source, \%env );
415             }
416             }
417              
418             sub inline_check {
419 468     468 0 2983 my $self = shift;
420              
421 468 50       927 die 'Cannot inline' unless $self->can_be_inlined;
422              
423 468         2791 my $type = $self->_self_or_first_inlinable_ancestor;
424 468         1451 return $type->_inline_generator->( $type, @_ );
425             }
426              
427             # For some idiotic reason I called $type->_subify directly in Code::TidyAll so
428             # I'll leave this in here for now.
429              
430             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
431 0     0   0 sub _subify { $_[0]->_subification }
432             ## use critic
433              
434             sub _build_subification {
435 78     78   24002 my $self = shift;
436              
437 78 100 66     527 if ( defined &Sub::Quote::quote_sub && $self->can_be_inlined ) {
438 72         901 return Sub::Quote::quote_sub( $self->inline_assert('$_[0]') );
439             }
440             else {
441 6     217   142 return sub { $self->validate_or_die( $_[0] ) };
  217         2895  
442             }
443             }
444              
445             sub _inline_throw_exception {
446 126     126   252 shift;
447 126         258 my $value_var = shift;
448 126         277 my $message_generator_var_name = shift;
449 126         226 my $type_var_name = shift;
450              
451             #<<<
452 126         638 return 'Specio::Exception->throw( '
453             . ' message => ' . $message_generator_var_name . '->(' . $value_var . '),'
454             . ' type => ' . $type_var_name . ','
455             . ' value => ' . $value_var . ' )';
456             #>>>
457             }
458              
459             # This exists for the benefit of Moo
460             sub coercion_sub {
461 5     5 0 10 my $self = shift;
462              
463 5 100 66     43 if ( defined &Sub::Quote::quote_sub
464 6     6   23 && all { $_->can_be_inlined } $self->coercions ) {
465              
466 3         40 my $inline = q{};
467 3         5 my %env;
468              
469 3         7 for my $coercion ( $self->coercions ) {
470 4         18 $inline .= sprintf(
471             '$_[0] = %s if %s;' . "\n",
472             $coercion->inline_coercion('$_[0]'),
473             $coercion->from->inline_check('$_[0]')
474             );
475              
476             %env = (
477             %env,
478 4         14 %{ $coercion->inline_environment },
479 4         12 %{ $coercion->from->inline_environment },
  4         23  
480             );
481             }
482              
483 3         30 $inline .= sprintf( "%s;\n", '$_[0]' );
484              
485 3         16 return Sub::Quote::quote_sub( $inline, \%env );
486             }
487             else {
488 2     3   26 return sub { $self->coerce_value(shift) };
  3         2423  
489             }
490             }
491              
492             sub _build_ancestors {
493 449     449   2789 my $self = shift;
494              
495 449         720 my @parents;
496              
497 449         724 my $type = $self;
498 449         1381 while ( $type = $type->parent ) {
499 1148         2992 push @parents, $type;
500             }
501              
502 449         2882 return \@parents;
503              
504             }
505              
506             sub _build_description {
507 904     904   5048 my $self = shift;
508              
509 904 100       2179 my $desc
510             = $self->is_anon ? 'anonymous type' : 'type named ' . $self->name;
511              
512 904         8814 $desc .= q{ } . $self->declared_at->description;
513              
514 904         2879 return $desc;
515             }
516              
517 908     908   21831 sub _build_coercions { {} }
518              
519             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
520             sub _clone_coercions {
521 9159     9159   13635 my $self = shift;
522              
523 9159         19288 my $coercions = $self->_coercions;
524 9159         28017 my %clones;
525              
526 9159         12058 for my $name ( keys %{$coercions} ) {
  9159         17453  
527 1         3 my $coercion = $coercions->{$name};
528 1         4 $clones{$name} = $coercion->clone_with_new_to($self);
529             }
530              
531 9159         23913 return \%clones;
532             }
533             ## use critic
534              
535             sub _stringify {
536 54     54   34458 my $self = shift;
537              
538 54 100       135 return $self->name unless $self->is_anon;
539              
540 11         55 return sprintf( '__ANON__(%s)', $self->parent . q{} );
541             }
542              
543             sub _build_signature {
544 74     74   777 my $self = shift;
545              
546             # This assumes that when a type is cloned, the underlying constraint or
547             # generator sub is copied by _reference_, so it has the same memory
548             # address and stringifies to the same value. XXX - will this break under
549             # threads?
550 74 100       179 return join "\n",
    100          
551             ( $self->_has_parent ? $self->parent->_signature : () ),
552             (
553             defined $self->_constraint
554             ? $self->_constraint
555             : $self->_inline_generator
556             );
557             }
558              
559             # Moose compatibility methods - these exist as a temporary hack to make Specio
560             # work with Moose.
561              
562             sub has_coercion {
563 0     0 0 0 shift->has_coercions;
564             }
565              
566             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
567             sub _inline_check {
568 114     114   463 shift->inline_check(@_);
569             }
570              
571             sub _compiled_type_constraint {
572 0     0   0 shift->_optimized_constraint;
573             }
574             ## use critic;
575              
576             # This class implements the methods that Moose expects from coercions as well.
577             sub coercion {
578 0     0 0 0 return shift;
579             }
580              
581             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
582             sub _compiled_type_coercion {
583 0     0   0 my $self = shift;
584              
585             return sub {
586 0     0   0 return $self->coerce_value(shift);
587 0         0 };
588             }
589             ## use critic
590              
591             sub has_message {
592 0     0 0 0 1;
593             }
594              
595             sub message {
596 0     0 0 0 shift->_message_generator;
597             }
598              
599             sub get_message {
600 0     0 0 0 my $self = shift;
601 0         0 my $value = shift;
602              
603 0         0 return $self->_message_generator->( $self, $value );
604             }
605              
606             sub check {
607 50     50 0 136 shift->value_is_valid(@_);
608             }
609              
610             sub coerce {
611 0     0 0   shift->coerce_value(@_);
612             }
613              
614             1;
615              
616             # ABSTRACT: The interface all type constraints should provide
617              
618             __END__
619              
620             =pod
621              
622             =encoding UTF-8
623              
624             =head1 NAME
625              
626             Specio::Constraint::Role::Interface - The interface all type constraints should provide
627              
628             =head1 VERSION
629              
630             version 0.47
631              
632             =head1 DESCRIPTION
633              
634             This role defines the interface that all type constraints must provide, and
635             provides most (or all) of the implementation. The L<Specio::Constraint::Simple>
636             class simply consumes this role and provides no additional code. Other
637             constraint classes add features or override some of this role's functionality.
638              
639             =for Pod::Coverage .*
640              
641             =head1 API
642              
643             See the L<Specio::Constraint::Simple> documentation for details. See the
644             internals of various constraint classes to see how this role can be overridden
645             or expanded upon.
646              
647             =head1 ROLES
648              
649             This role does the L<Specio::Role::Inlinable> role.
650              
651             =head1 SUPPORT
652              
653             Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
654              
655             I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
656              
657             =head1 SOURCE
658              
659             The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
660              
661             =head1 AUTHOR
662              
663             Dave Rolsky <autarch@urth.org>
664              
665             =head1 COPYRIGHT AND LICENSE
666              
667             This software is Copyright (c) 2012 - 2021 by Dave Rolsky.
668              
669             This is free software, licensed under:
670              
671             The Artistic License 2.0 (GPL Compatible)
672              
673             The full text of the license can be found in the
674             F<LICENSE> file included with this distribution.
675              
676             =cut