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 32     32   7901 use strict;
  32         60  
  32         1151  
4 32     32   146 use warnings;
  32         59  
  32         2243  
5              
6             our $VERSION = '0.53';
7              
8 32     32   186 use Carp qw( confess );
  32         104  
  32         1845  
9 32     32   9673 use Eval::Closure qw( eval_closure );
  32         34286  
  32         2684  
10 32     32   286 use List::Util 1.33 qw( all any first );
  32         743  
  32         2454  
11 32     32   14807 use Specio::Exception;
  32         96  
  32         1471  
12 32     32   228 use Specio::PartialDump qw( partial_dump );
  32         77  
  32         1796  
13 32     32   186 use Specio::TypeChecks qw( is_CodeRef );
  32         88  
  32         2152  
14              
15 32     32   213 use Role::Tiny 1.003003;
  32         716  
  32         253  
16              
17 32     32   20034 use Specio::Role::Inlinable;
  32         96  
  32         2099  
18             with 'Specio::Role::Inlinable';
19              
20             use overload(
21             q{""} => '_stringify',
22             '&{}' => '_subification',
23 4042     4042   18900 'bool' => sub {1},
24 32         495 'eq' => 'is_same_type_as',
25 32     32   198 );
  32         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 9002     9002   17179 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 971     971   16106 my $self = shift;
97 971         1475 my $p = shift;
98              
99 971 100 100     2740 unless ( $self->_has_constraint || $self->_has_inline_generator ) {
100 29         296 $self->{_constraint} = $NullConstraint;
101             }
102              
103             die
104 971 50 66     8800 '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 971         6787 = $self->_wrap_message_generator( $p->{message_generator} );
109              
110 971         2999 return;
111             }
112             ## use critic
113              
114             sub _wrap_message_generator {
115 946     946   1495 my $self = shift;
116 946         2333 my $generator = shift;
117              
118 946 50       2171 unless ( defined $generator ) {
119             $generator = sub {
120 2359     2359   6068 my $description = shift;
121 2359         5121 my $value = shift;
122              
123 2359         14547 return "Validation failed for $description with value "
124             . partial_dump($value);
125 946         6473 };
126             }
127              
128 946         3134 my $d = $self->description;
129              
130 946     2359   8567 return sub { $generator->( $d, @_ ) };
  2359         215589  
131             }
132              
133 44     44 0 70 sub coercions { values %{ $_[0]->{_coercions} } }
  44         273  
134 0     0 0 0 sub coercion_from_type { $_[0]->{_coercions}{ $_[1] } }
135 22     22   94 sub _has_coercion_from_type { exists $_[0]->{_coercions}{ $_[1] } }
136 18     18   59 sub _add_coercion { $_[0]->{_coercions}{ $_[1] } = $_[2] }
137 63     63 0 4533 sub has_coercions { scalar keys %{ $_[0]->{_coercions} } }
  63         529  
138              
139             sub validate_or_die {
140 308     308 0 4020 my $self = shift;
141 308         750 my $value = shift;
142              
143 308 100       978 return if $self->value_is_valid($value);
144              
145 261         14153 Specio::Exception->throw(
146             message => $self->_message_generator->($value),
147             type => $self,
148             value => $value,
149             );
150             }
151              
152             sub value_is_valid {
153 3333     3333 0 75168 my $self = shift;
154 3333         7411 my $value = shift;
155              
156 3333         37689 return $self->_optimized_constraint->($value);
157             }
158              
159             sub _ancestors_and_self {
160 920     920   1440 my $self = shift;
161              
162 920         1504 return ( ( reverse @{ $self->_ancestors } ), $self );
  920         2592  
163             }
164              
165             sub is_a_type_of {
166 13     13 0 17 my $self = shift;
167 13         12 my $type = shift;
168              
169             return
170 40     40   356 any { $_->_signature eq $type->_signature }
171 13         46 $self->_ancestors_and_self;
172             }
173              
174             sub is_same_type_as {
175 16     16 0 12837 my $self = shift;
176 16         29 my $type = shift;
177              
178 16         63 return $self->_signature eq $type->_signature;
179             }
180              
181             sub is_anon {
182 1040     1040 0 19792 my $self = shift;
183              
184 1040         2745 return !$self->_has_name;
185             }
186              
187             sub has_real_constraint {
188 405     405 0 814 my $self = shift;
189              
190 405   100     1318 return ( $self->_has_constraint && $self->_constraint ne $NullConstraint )
191             || $self->_has_inline_generator;
192             }
193              
194             sub can_be_inlined {
195 1267     1267 0 2474 my $self = shift;
196              
197 1267 100       3028 return 1 if $self->_has_inline_generator;
198 81 100 66     584 return 0
199             if $self->_has_constraint && $self->_constraint ne $NullConstraint;
200              
201             # If this type is an empty subtype of an inlinable parent, then we can
202             # inline this type as well.
203 45 50 33     573 return 1 if $self->_has_parent && $self->parent->can_be_inlined;
204 0         0 return 0;
205             }
206              
207             sub _build_generated_inline_sub {
208 351     351   2136 my $self = shift;
209              
210 351         921 my $type = $self->_self_or_first_inlinable_ancestor;
211              
212 351         1060 my $source
213             = 'sub { ' . $type->_inline_generator->( $type, '$_[0]' ) . '}';
214              
215 351         1548 return eval_closure(
216             source => $source,
217             environment => $type->inline_environment,
218             description => 'inlined sub for ' . $self->description,
219             );
220             }
221              
222             sub _self_or_first_inlinable_ancestor {
223 819     819   1362 my $self = shift;
224              
225 844     844   6824 my $type = first { $_->_has_inline_generator }
226 819         3543 reverse $self->_ancestors_and_self;
227              
228             # This should never happen because ->can_be_inlined should always be
229             # checked before this builder is called.
230 819 50       5196 die 'Cannot generate an inline sub' unless $type;
231              
232 819         1685 return $type;
233             }
234              
235             sub _build_optimized_constraint {
236 110     110   1001 my $self = shift;
237              
238 110 100       482 if ( $self->can_be_inlined ) {
239 100         912 return $self->_generated_inline_sub;
240             }
241             else {
242 10         248 return $self->_constraint_with_parents;
243             }
244             }
245              
246             sub _constraint_with_parents {
247 88     88   271 my $self = shift;
248              
249 88         186 my @constraints;
250 88         379 for my $type ( $self->_ancestors_and_self ) {
251 405 100       89454 next unless $type->has_real_constraint;
252              
253             # If a type can be inlined, we can use that and discard all of the
254             # ancestors we've seen so far, since we can assume that the inlined
255             # constraint does all of the ancestor checks in addition to its own.
256 397 100       5253 if ( $type->can_be_inlined ) {
257 383         6359 @constraints = $type->_generated_inline_sub;
258             }
259             else {
260 14         213 push @constraints, $type->_constraint;
261             }
262             }
263              
264 88 50       40818 return $NullConstraint unless @constraints;
265              
266             return sub {
267 1267     1267   11001 all { $_->( $_[0] ) } @constraints;
  1669         13336  
268 88         1479 };
269             }
270              
271             # This is only used for identifying from types as part of coercions, but I
272             # want to leave open the possibility of using something other than
273             # _description in the future.
274             sub id {
275 22     22 0 77 my $self = shift;
276              
277 22         71 return $self->description;
278             }
279              
280             sub add_coercion {
281 18     18 0 107 my $self = shift;
282 18         39 my $coercion = shift;
283              
284 18         51 my $from_id = $coercion->from->id;
285              
286 18 50       156 confess "Cannot add two coercions fom the same type: $from_id"
287             if $self->_has_coercion_from_type($from_id);
288              
289 18         60 $self->_add_coercion( $from_id => $coercion );
290              
291 18         54 return;
292             }
293              
294             sub has_coercion_from_type {
295 4     4 0 7 my $self = shift;
296 4         5 my $type = shift;
297              
298 4         8 return $self->_has_coercion_from_type( $type->id );
299             }
300              
301             sub coerce_value {
302 16     16 0 2158 my $self = shift;
303 16         27 my $value = shift;
304              
305 16         44 for my $coercion ( $self->coercions ) {
306 22 100       213 next unless $coercion->from->value_is_valid($value);
307              
308 11         2750 return $coercion->coerce($value);
309             }
310              
311 5         88 return $value;
312             }
313              
314             sub can_inline_coercion {
315 5     5 0 9 my $self = shift;
316              
317 5     8   24 return all { $_->can_be_inlined } $self->coercions;
  8         33  
318             }
319              
320             sub can_inline_coercion_and_check {
321 11     11 0 20 my $self = shift;
322              
323 11     22   59 return all { $_->can_be_inlined } $self, $self->coercions;
  22         102  
324             }
325              
326             sub inline_coercion {
327 2     2 0 762 my $self = shift;
328 2         3 my $arg_name = shift;
329              
330 2 50       3 die 'Cannot inline coercion'
331             unless $self->can_inline_coercion;
332              
333 2         14 my $source = 'do { my $value = ' . $arg_name . ';';
334              
335 2         3 my ( $coerce, $env );
336 2         5 ( $coerce, $arg_name, $env ) = $self->_inline_coercion($arg_name);
337 2         5 $source .= $coerce . $arg_name . '};';
338              
339 2         6 return ( $source, $env );
340             }
341              
342             sub inline_coercion_and_check {
343 6     6 0 784 my $self = shift;
344 6         13 my $arg_name = shift;
345              
346 6 50       19 die 'Cannot inline coercion and check'
347             unless $self->can_inline_coercion_and_check;
348              
349 6         56 my $source = 'do { my $value = ' . $arg_name . ';';
350              
351 6         9 my ( $coerce, $env );
352 6         20 ( $coerce, $arg_name, $env ) = $self->_inline_coercion($arg_name);
353 6         22 my ( $assert, $assert_env ) = $self->inline_assert($arg_name);
354              
355 6         14 $source .= $coerce;
356 6         20 $source .= $assert;
357 6         11 $source .= $arg_name . '};';
358              
359 6         11 return ( $source, { %{$env}, %{$assert_env} } );
  6         12  
  6         40  
360             }
361              
362             sub _inline_coercion {
363 8     8   13 my $self = shift;
364 8         13 my $arg_name = shift;
365              
366 8 100       41 return ( q{}, $arg_name, {} ) unless $self->has_coercions;
367              
368 4         8 my %env;
369              
370 4         8 $arg_name = '$value';
371 4         9 my $source = $arg_name . ' = ';
372 4         10 for my $coercion ( $self->coercions ) {
373 9         91 $source
374             .= '('
375             . $coercion->from->inline_check($arg_name) . ') ? ('
376             . $coercion->inline_coercion($arg_name) . ') : ';
377              
378             %env = (
379             %env,
380 9         28 %{ $coercion->inline_environment },
381 9         66 %{ $coercion->from->inline_environment },
  9         45  
382             );
383             }
384 4         29 $source .= $arg_name . ';';
385              
386 4         27 return ( $source, $arg_name, \%env );
387             }
388              
389             {
390             my $counter = 1;
391              
392             sub inline_assert {
393 126     126 0 262 my $self = shift;
394              
395 126         324 my $type_var_name = '$_Specio_Constraint_Interface_type' . $counter;
396 126         247 my $message_generator_var_name
397             = '$_Specio_Constraint_Interface_message_generator' . $counter;
398             my %env = (
399             $type_var_name => \$self,
400             $message_generator_var_name => \( $self->_message_generator ),
401 126         574 %{ $self->inline_environment },
  126         767  
402             );
403              
404 126         1247 my $source = $self->inline_check( $_[0] );
405 126         547 $source .= ' or ';
406 126         512 $source .= $self->_inline_throw_exception(
407             $_[0],
408             $message_generator_var_name,
409             $type_var_name
410             );
411 126         266 $source .= ';';
412              
413 126         257 $counter++;
414              
415 126         681 return ( $source, \%env );
416             }
417             }
418              
419             sub inline_check {
420 468     468 0 3020 my $self = shift;
421              
422 468 50       1152 die 'Cannot inline' unless $self->can_be_inlined;
423              
424 468         2808 my $type = $self->_self_or_first_inlinable_ancestor;
425 468         1354 return $type->_inline_generator->( $type, @_ );
426             }
427              
428             # For some idiotic reason I called $type->_subify directly in Code::TidyAll so
429             # I'll leave this in here for now.
430              
431             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
432 0     0   0 sub _subify { $_[0]->_subification }
433             ## use critic
434              
435             sub _build_subification {
436 78     78   31815 my $self = shift;
437              
438 78 100 66     569 if ( defined &Sub::Quote::quote_sub && $self->can_be_inlined ) {
439 72         875 return Sub::Quote::quote_sub( $self->inline_assert('$_[0]') );
440             }
441             else {
442 6     217   126 return sub { $self->validate_or_die( $_[0] ) };
  217         4773  
443             }
444             }
445              
446             sub _inline_throw_exception {
447 126     126   220 shift;
448 126         276 my $value_var = shift;
449 126         222 my $message_generator_var_name = shift;
450 126         219 my $type_var_name = shift;
451              
452             #<<<
453 126         603 return 'Specio::Exception->throw( '
454             . ' message => ' . $message_generator_var_name . '->(' . $value_var . '),'
455             . ' type => ' . $type_var_name . ','
456             . ' value => ' . $value_var . ' )';
457             #>>>
458             }
459              
460             # This exists for the benefit of Moo
461             sub coercion_sub {
462 5     5 0 9 my $self = shift;
463              
464 5 100 66     34 if ( defined &Sub::Quote::quote_sub
465 6     6   26 && all { $_->can_be_inlined } $self->coercions ) {
466              
467 3         26 my $inline = q{};
468 3         5 my %env;
469              
470 3         7 for my $coercion ( $self->coercions ) {
471 4         17 $inline .= sprintf(
472             '$_[0] = %s if %s;' . "\n",
473             $coercion->inline_coercion('$_[0]'),
474             $coercion->from->inline_check('$_[0]')
475             );
476              
477             %env = (
478             %env,
479 4         13 %{ $coercion->inline_environment },
480 4         9 %{ $coercion->from->inline_environment },
  4         25  
481             );
482             }
483              
484 3         31 $inline .= sprintf( "%s;\n", '$_[0]' );
485              
486 3         17 return Sub::Quote::quote_sub( $inline, \%env );
487             }
488             else {
489 2     3   23 return sub { $self->coerce_value(shift) };
  3         3202  
490             }
491             }
492              
493             sub _build_ancestors {
494 450     450   2807 my $self = shift;
495              
496 450         688 my @parents;
497              
498 450         711 my $type = $self;
499 450         1586 while ( $type = $type->parent ) {
500 1153         3233 push @parents, $type;
501             }
502              
503 450         2705 return \@parents;
504              
505             }
506              
507             sub _build_description {
508 967     967   5301 my $self = shift;
509              
510 967 100       2440 my $desc
511             = $self->is_anon ? 'anonymous type' : 'type named ' . $self->name;
512              
513 967         9474 $desc .= q{ } . $self->declared_at->description;
514              
515 967         3412 return $desc;
516             }
517              
518 971     971   25914 sub _build_coercions { {} }
519              
520             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
521             sub _clone_coercions {
522 9341     9341   14219 my $self = shift;
523              
524 9341         23982 my $coercions = $self->_coercions;
525 9341         32290 my %clones;
526              
527 9341         12537 for my $name ( keys %{$coercions} ) {
  9341         21870  
528 1         2 my $coercion = $coercions->{$name};
529 1         4 $clones{$name} = $coercion->clone_with_new_to($self);
530             }
531              
532 9341         26155 return \%clones;
533             }
534             ## use critic
535              
536             sub _stringify {
537 54     54   148021 my $self = shift;
538              
539 54 100       106 return $self->name unless $self->is_anon;
540              
541 11         41 return sprintf( '__ANON__(%s)', $self->parent . q{} );
542             }
543              
544             sub _build_signature {
545 79     79   721 my $self = shift;
546              
547             # This assumes that when a type is cloned, the underlying constraint or
548             # generator sub is copied by _reference_, so it has the same memory
549             # address and stringifies to the same value. XXX - will this break under
550             # threads?
551 79 100       165 return join "\n",
    100          
552             ## no critic (Subroutines::ProtectPrivateSubs)
553             ( $self->_has_parent ? $self->parent->_signature : () ),
554             (
555             defined $self->_constraint
556             ? $self->_constraint
557             : $self->_inline_generator
558             );
559             }
560              
561             # Moose compatibility methods - these exist as a temporary hack to make Specio
562             # work with Moose.
563              
564             sub has_coercion {
565 0     0 0 0 shift->has_coercions;
566             }
567              
568             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
569             sub _inline_check {
570 114     114   428 shift->inline_check(@_);
571             }
572              
573             sub _compiled_type_constraint {
574 0     0   0 shift->_optimized_constraint;
575             }
576             ## use critic;
577              
578             # This class implements the methods that Moose expects from coercions as well.
579             sub coercion {
580 0     0 0 0 return shift;
581             }
582              
583             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
584             sub _compiled_type_coercion {
585 0     0   0 my $self = shift;
586              
587             return sub {
588 0     0   0 return $self->coerce_value(shift);
589 0         0 };
590             }
591             ## use critic
592              
593             sub has_message {
594 0     0 0 0 1;
595             }
596              
597             sub message {
598 0     0 0 0 shift->_message_generator;
599             }
600              
601             sub get_message {
602 0     0 0 0 my $self = shift;
603 0         0 my $value = shift;
604              
605 0         0 return $self->_message_generator->( $self, $value );
606             }
607              
608             sub check {
609 54     54 0 2601 shift->value_is_valid(@_);
610             }
611              
612             sub coerce {
613 0     0 0   shift->coerce_value(@_);
614             }
615              
616             1;
617              
618             # ABSTRACT: The interface all type constraints should provide
619              
620             __END__
621              
622             =pod
623              
624             =encoding UTF-8
625              
626             =head1 NAME
627              
628             Specio::Constraint::Role::Interface - The interface all type constraints should provide
629              
630             =head1 VERSION
631              
632             version 0.53
633              
634             =head1 DESCRIPTION
635              
636             This role defines the interface that all type constraints must provide, and
637             provides most (or all) of the implementation. The L<Specio::Constraint::Simple>
638             class simply consumes this role and provides no additional code. Other
639             constraint classes add features or override some of this role's functionality.
640              
641             =for Pod::Coverage .*
642              
643             =head1 API
644              
645             See the L<Specio::Constraint::Simple> documentation for details. See the
646             internals of various constraint classes to see how this role can be overridden
647             or expanded upon.
648              
649             =head1 ROLES
650              
651             This role does the L<Specio::Role::Inlinable> role.
652              
653             =head1 SUPPORT
654              
655             Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
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 - 2025 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