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   2093519 use v5.14.0;
  11         93  
4 11     11   63 use warnings;
  11         21  
  11         521  
5              
6             our $VERSION = "0.05";
7              
8 11     11   4561 use Class::Load qw(load_class try_load_class is_class_loaded);
  11         175821  
  11         761  
9 11     11   138 use Scalar::Util qw(blessed);
  11         27  
  11         465  
10 11     11   4922 use Import::Into;
  11         5546  
  11         11932  
11              
12             sub import {
13 20     20   99658 my $class = shift;
14 20         64 my @interface_packages = @_;
15 20         76 my ($pkg, $filename, $line) = caller;
16              
17 20         58 for (@interface_packages) {
18 11         33 _register_check_list($pkg, $_, $filename, $line);
19             }
20              
21 20         132 Function::Parameters->import::into($pkg);
22 20         49263 Function::Return->import::into($pkg);
23             }
24              
25             our @CHECK_LIST;
26             my %IMPL_CHECKED;
27             CHECK {
28 11     11   80313 for (@CHECK_LIST) {
29 11         66 assert_valid(@$_{qw/package interface_package filename line/});
30              
31             # for Function::Interface::Types#ImplOf
32 11         2434 $IMPL_CHECKED{$_->{package}}{$_->{interface_package}} = !!1;
33             }
34             }
35              
36             sub _register_check_list {
37 12     12   120 my ($package, $interface_package, $filename, $line) = @_;
38              
39 12         66 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 9999 my ($package, $interface_package, $filename, $line) = @_;
49 19         56 my @fl = ($filename, $line);
50              
51             {
52 19         68 my $ok = is_class_loaded($package);
53 19 100       1197 return _error("implements package is not loaded yet. required to use $package", @fl) if !$ok;
54             }
55              
56             {
57 19         36 my ($ok, $e) = try_load_class($interface_package);
  18         31  
  18         60  
58 18 100       2210 return _error("cannot load interface package: $e", @fl) if !$ok;
59             }
60              
61 17 100       75 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         66 my $fname = $ifunction_info->subname;
66 20         78 my $def = $ifunction_info->definition;
67              
68 20 100       254 my $code = $package->can($fname)
69             or return _error("function `$fname` is required. Interface: $def", @fl);
70              
71 19 100       56 my $pinfo = info_params($code)
72             or return _error("cannot get function `$fname` parameters info. Interface: $def", @fl);
73 17 100       1792 my $rinfo = info_return($code)
74             or return _error("cannot get function `$fname` return info. Interface: $def", @fl);
75              
76 15 100       2657 check_params($pinfo, $ifunction_info)
77             or return _error("function `$fname` is invalid parameters. Interface: $def", @fl);
78 13 100       41 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   103 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 129 my $interface_package = shift;
90 18         91 load_class('Function::Interface');
91 18         1986 Function::Interface::info($interface_package)
92             }
93              
94             sub info_params {
95 20     20 1 121 my $code = shift;
96 20         62 load_class('Function::Parameters');
97 20         1455 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 122 my $code = shift;
115 18         60 load_class('Function::Return');
116 18         1230 Function::Return::info($code)
117             }
118              
119             sub check_params {
120 33     33 1 93 my ($pinfo, $ifunction_info) = @_;
121              
122 33 100       86 return unless $ifunction_info->keyword eq $pinfo->keyword;
123              
124 31         353 my $params_count = 0;
125 31         72 for my $key (qw/positional_required positional_optional named_required named_optional/) {
126 112         415 my @params = $pinfo->$key;
127 112         1483 $params_count += @params;
128              
129 112         154 for my $i (0 .. $#{$ifunction_info->$key}) {
  112         321  
130 13         42 my $ifp = $ifunction_info->$key->[$i];
131 13         21 my $p = $params[$i];
132 13 100       25 return unless check_param($p, $ifp);
133             }
134             }
135              
136 25 100       82 return unless $params_count == @{$ifunction_info->params};
  25         53  
137 22         75 return !!1
138             }
139              
140             sub check_param {
141 13     13 1 24 my ($param, $iparam) = @_;
142 13 100       74 return unless $param;
143 9   100     136 return $iparam->type eq $param->type
144             && $iparam->name eq $param->name
145             }
146              
147             sub check_return {
148 25     25 1 81 my ($rinfo, $ifunction_info) = @_;
149              
150 25 100       44 return unless @{$rinfo->types} == @{$ifunction_info->return};
  25         55  
  25         114  
151              
152 18         47 for my $i (0 .. $#{$ifunction_info->return}) {
  18         37  
153 8         51 my $ifr = $ifunction_info->return->[$i];
154 8         17 my $type = $rinfo->types->[$i];
155 8 100       39 return unless $ifr->type eq $type;
156             }
157 14         156 return !!1;
158             }
159              
160             sub impl_of {
161 14     14 1 14120 my ($package, $interface_package) = @_;
162 14 100       103 $package = ref $package ? blessed($package) : $package;
163 14         74 $IMPL_CHECKED{$package}{$interface_package}
164             }
165              
166             1;
167             __END__