File Coverage

blib/lib/Function/Interface/Impl.pm
Criterion Covered Total %
statement 85 85 100.0
branch 30 30 100.0
condition 3 3 100.0
subroutine 17 17 100.0
pod 8 8 100.0
total 143 143 100.0


line stmt bran cond sub pod time code
1             package Function::Interface::Impl;
2              
3 11     11   2025852 use v5.14.0;
  11         89  
4 11     11   62 use warnings;
  11         20  
  11         496  
5              
6             our $VERSION = "0.04";
7              
8 11     11   4353 use Class::Load qw(load_class try_load_class is_class_loaded);
  11         173539  
  11         774  
9 11     11   91 use Scalar::Util qw(blessed);
  11         24  
  11         465  
10 11     11   4746 use Import::Into;
  11         5460  
  11         11649  
11              
12             sub import {
13 20     20   82744 my $class = shift;
14 20         58 my @interface_packages = @_;
15 20         78 my ($pkg, $filename, $line) = caller;
16              
17 20         56 for (@interface_packages) {
18 11         29 _register_check_list($pkg, $_, $filename, $line);
19             }
20              
21 20         138 Function::Parameters->import::into($pkg);
22 20         47699 Function::Return->import::into($pkg);
23             }
24              
25             our @CHECK_LIST;
26             my %IMPL_CHECKED;
27             CHECK {
28 11     11   78724 for (@CHECK_LIST) {
29 11         68 assert_valid(@$_{qw/package interface_package filename line/});
30              
31             # for Function::Interface::Types#ImplOf
32 11         2145 $IMPL_CHECKED{$_->{package}}{$_->{interface_package}} = !!1;
33             }
34             }
35              
36             sub _register_check_list {
37 12     12   115 my ($package, $interface_package, $filename, $line) = @_;
38              
39 12         71 push @CHECK_LIST => +{
40             package => $package,
41             interface_package => $interface_package,
42             filename => $filename,
43             line => $line,
44             }
45             }
46              
47             sub assert_valid {
48 19     19 1 9170 my ($package, $interface_package, $filename, $line) = @_;
49 19         51 my @fl = ($filename, $line);
50              
51             {
52 19         59 my $ok = is_class_loaded($package);
53 19 100       1093 return _error("implements package is not loaded yet. required to use $package", @fl) if !$ok;
54             }
55              
56             {
57 19         26 my ($ok, $e) = try_load_class($interface_package);
  18         31  
  18         49  
58 18 100       1841 return _error("cannot load interface package: $e", @fl) if !$ok;
59             }
60              
61 17 100       77 my $iinfo = info_interface($interface_package)
62             or return _error("cannot get interface info", @fl);
63              
64 16         35 for my $ifunction_info (@{$iinfo->functions}) {
  16         58  
65 20         63 my $fname = $ifunction_info->subname;
66 20         63 my $def = $ifunction_info->definition;
67              
68 20 100       247 my $code = $package->can($fname)
69             or return _error("function `$fname` is required. Interface: $def", @fl);
70              
71 19 100       52 my $pinfo = info_params($code)
72             or return _error("cannot get function `$fname` parameters info. Interface: $def", @fl);
73 17 100       1662 my $rinfo = info_return($code)
74             or return _error("cannot get function `$fname` return info. Interface: $def", @fl);
75              
76 15 100       2473 check_params($pinfo, $ifunction_info)
77             or return _error("function `$fname` is invalid parameters. Interface: $def", @fl);
78 13 100       42 check_return($rinfo, $ifunction_info)
79             or return _error("function `$fname` is invalid return. Interface: $def", @fl);
80             }
81             }
82              
83             sub _error {
84 1     1   109 my ($msg, $filename, $line) = @_;
85 1         13 die sprintf "implements error: %s at %s line %s\n\tdied", $msg, $filename, $line;
86             }
87              
88             sub info_interface {
89 18     18 1 131 my $interface_package = shift;
90 18         83 load_class('Function::Interface');
91 18         1790 Function::Interface::info($interface_package)
92             }
93              
94             sub info_params {
95 20     20 1 129 my $code = shift;
96 20         64 load_class('Function::Parameters');
97 20         1364 Function::Parameters::info($code)
98             }
99              
100              
101             # XXX:
102             # Need to call C code blocks in the following order:
103             # 1. Function::Return#CHECK (to get return info)
104             # 2. Function::Interface::Impl#CHECK (to check implements)
105             #
106             # C code blocks are LIFO order.
107             # So, it is necessary to load in the following order:
108             # 1. Function::Interface::Impl
109             # 2. Function::Return
110             #
111             # Because of this,
112             # Function::Interface::Impl doesn't use Function::Return, but loads dat run time.
113             sub info_return {
114 18     18 1 123 my $code = shift;
115 18         58 load_class('Function::Return');
116 18         1221 Function::Return::info($code)
117             }
118              
119             sub check_params {
120 33     33 1 94 my ($pinfo, $ifunction_info) = @_;
121              
122 33 100       80 return unless $ifunction_info->keyword eq $pinfo->keyword;
123              
124 31         351 my $params_count = 0;
125 31         64 for my $key (qw/positional_required positional_optional named_required named_optional/) {
126 112         400 my @params = $pinfo->$key;
127 112         1476 $params_count += @params;
128              
129 112         176 for my $i (0 .. $#{$ifunction_info->$key}) {
  112         288  
130 13         40 my $ifp = $ifunction_info->$key->[$i];
131 13         21 my $p = $params[$i];
132 13 100       27 return unless check_param($p, $ifp);
133             }
134             }
135              
136 25 100       73 return unless $params_count == @{$ifunction_info->params};
  25         55  
137 22         73 return !!1
138             }
139              
140             sub check_param {
141 13     13 1 25 my ($param, $iparam) = @_;
142 13 100       74 return unless $param;
143 9   100     134 return $iparam->type eq $param->type
144             && $iparam->name eq $param->name
145             }
146              
147             sub check_return {
148 25     25 1 77 my ($rinfo, $ifunction_info) = @_;
149              
150 25 100       45 return unless @{$rinfo->types} == @{$ifunction_info->return};
  25         62  
  25         116  
151              
152 18         36 for my $i (0 .. $#{$ifunction_info->return}) {
  18         37  
153 8         50 my $ifr = $ifunction_info->return->[$i];
154 8         18 my $type = $rinfo->types->[$i];
155 8 100       30 return unless $ifr->type eq $type;
156             }
157 14         156 return !!1;
158             }
159              
160             sub impl_of {
161 14     14 1 15067 my ($package, $interface_package) = @_;
162 14 100       46 $package = ref $package ? blessed($package) : $package;
163 14         69 $IMPL_CHECKED{$package}{$interface_package}
164             }
165              
166             1;
167             __END__