File Coverage

blib/lib/Specio/Constraint/Role/Interface.pm
Criterion Covered Total %
statement 227 242 93.8
branch 36 46 78.2
condition 15 21 71.4
subroutine 63 74 85.1
pod 0 28 0.0
total 341 411 82.9


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