File Coverage

blib/lib/Type/Alias.pm
Criterion Covered Total %
statement 97 97 100.0
branch 26 26 100.0
condition 13 16 81.2
subroutine 24 24 100.0
pod 0 2 0.0
total 160 165 96.9


line stmt bran cond sub pod time code
1             package Type::Alias;
2 12     12   1833878 use strict;
  12         133  
  12         356  
3 12     12   65 use warnings;
  12         37  
  12         504  
4              
5             our $VERSION = "0.05";
6              
7 12     12   64 use feature qw(state);
  12         23  
  12         1670  
8 12     12   75 use Carp qw(croak);
  12         23  
  12         692  
9 12     12   98 use Scalar::Util qw(blessed);
  12         31  
  12         571  
10 12     12   3990 use Types::Standard qw(Dict Tuple);
  12         692986  
  12         126  
11              
12             sub import {
13 15     15   3990 my ($class, %args) = @_;
14              
15 15         46 my $target_package = caller;
16              
17 15         76 $class->_define_type($target_package, $args{type});
18 14         84 $class->_predefine_type_aliases($target_package, $args{'-alias'});
19 13         55 $class->_predefine_type_functions($target_package, $args{'-fun'});
20             }
21              
22             sub _define_type {
23 15     15   64 my ($class, $target_package, $options) = @_;
24 15   100     177 $options //= {};
25 15   100     85 my $type_name = $options->{'-as'} // 'type';
26              
27 15 100       204 if ($target_package->can($type_name)) {
28 1         108 croak "Alreay exists function '${target_package}::${type_name}'. Can specify another name: type => { -as => 'XXX' }.";
29             }
30              
31 12     12   30795 no strict qw(refs);
  12         32  
  12         487  
32 12     12   67 no warnings qw(once);
  12         29  
  12         937  
33 14         77 *{"${target_package}::${type_name}"} = sub {
34 17     17   57215 my ($alias_name, $type_args) = @_;
35              
36 12     12   76 no strict qw(refs);
  12         21  
  12         338  
37 12     12   77 no warnings qw(redefine); # Already define empty type alias at _import_type_aliases
  12         25  
  12         2307  
38 17         49 *{"${target_package}::${alias_name}"} = generate_type_alias($type_args);
  17         118  
39             }
40 14         95 }
41              
42             sub _predefine_type_aliases {
43 14     14   43 my ($class, $target_package, $type_aliases) = @_;
44 14   100     75 $type_aliases //= [];
45              
46 14         39 for my $alias_name (@$type_aliases) {
47 13 100       74 if ($target_package->can($alias_name)) {
48 1         146 croak "Cannot predeclare type alias '${target_package}::${alias_name}'.";
49             }
50              
51 12     12   106 no strict qw(refs);
  12         20  
  12         2451  
52 12         46 *{"${target_package}::${alias_name}"} = sub :prototype() {
53 1     1   2441 croak "You should define type alias '$alias_name' before using it."
54             }
55 12         48 }
56             }
57              
58             sub _predefine_type_functions {
59 13     13   47 my ($class, $target_package, $type_functions) = @_;
60 13   100     68 $type_functions //= [];
61              
62 13         547 for my $type_function (@$type_functions) {
63 5 100       39 if ($target_package->can($type_function)) {
64 1         105 croak "Cannot predeclare type function '${target_package}::${type_function}'.";
65             }
66              
67 12     12   86 no strict qw(refs);
  12         32  
  12         7744  
68 4         359 *{"${target_package}::${type_function}"} = sub :prototype(;$) {
69 1     1   5001 croak "You should define type function '$type_function' before using it."
70             }
71 4         16 }
72             }
73              
74             sub to_type {
75 110     110 0 115496 my $v = shift;
76 110 100       376 if (blessed($v)) {
    100          
77 84 100 66     277 if ($v->can('check') && $v->can('get_message')) {
78 82         1771 return $v;
79             }
80             else {
81 2         328 croak 'This object is not supported: '. ref $v;
82             }
83             }
84             elsif (ref $v) {
85 22 100       110 if (ref $v eq 'ARRAY') {
    100          
    100          
86 8         22 return Tuple[ map { to_type($_) } @$v ];
  14         30  
87             }
88             elsif (ref $v eq 'HASH') {
89             return Dict[
90 8         42 map { $_ => to_type($v->{$_}) } sort { $a cmp $b } keys %$v
  17         45  
  11         30  
91             ];
92             }
93             elsif (ref $v eq 'CODE') {
94             return sub {
95 23     23   4559 my @args;
96 23 100       64 if (@_) {
97 18 100 66     99 unless (@_ == 1 && ref $_[0] eq 'ARRAY') {
98 1         134 croak 'This type requires an array reference';
99             }
100 17         31 @args = map { to_type($_) } @{$_[0]};
  15         31  
  17         43  
101             }
102              
103 22         220 to_type($v->(@args));
104             }
105 5         35 }
106             else {
107 1         94 croak 'This reference is not supported: ' . ref $v ;
108             }
109             }
110             else {
111             # TODO: Is it better to make it a type that checks whether it matches the given value?
112 4 100       325 croak 'This value is not supported: ' . (defined $v ? $v : 'undef');
113             }
114             }
115              
116             sub generate_type_alias {
117 21     21 0 11692 my ($type_args) = @_;
118              
119 21 100 50     113 if ( (ref $type_args||'') eq 'CODE') {
120             return sub :prototype(;$) {
121 18     18   8686 state $type = to_type($type_args);
122 18         45 $type->(@_);
123 4         27 };
124             }
125             else {
126             return sub :prototype() {
127 47     47   81175 state $type = to_type($type_args);
128 47         38348 $type;
129             }
130 17         96 }
131             }
132              
133             1;
134             __END__