File Coverage

blib/lib/Function/Interface/Impl.pm
Criterion Covered Total %
statement 97 97 100.0
branch 30 30 100.0
condition 3 3 100.0
subroutine 22 22 100.0
pod 8 8 100.0
total 160 160 100.0


line stmt bran cond sub pod time code
1             package Function::Interface::Impl;
2              
3 12     12   2385067 use v5.14.0;
  12         103  
4 12     12   66 use warnings;
  12         27  
  12         608  
5              
6             our $VERSION = "0.06";
7              
8 12     12   4817 use Class::Load qw(try_load_class is_class_loaded);
  12         188747  
  12         819  
9 12     12   100 use Scalar::Util qw(blessed);
  12         26  
  12         581  
10 12     12   5228 use Import::Into;
  12         5955  
  12         356  
11 12     12   4629 use B::Hooks::EndOfScope;
  12         49973  
  12         86  
12              
13 12     12   5269 use Function::Interface;
  12         50  
  12         145  
14 12     12   7601 use Function::Parameters;
  12         35895  
  12         76  
15 12     12   10532 use Function::Return;
  12         61474  
  12         64  
16              
17             sub import {
18 21     21   11804 my $class = shift;
19 21         68 my @interface_packages = @_;
20 21         88 my ($pkg, $filename, $line) = caller;
21              
22 21         66 for (@interface_packages) {
23 12         39 _register_check_list($pkg, $_, $filename, $line);
24             }
25              
26 21         180 Function::Parameters->import::into($pkg);
27 21         13050 Function::Return->import::into($pkg);
28              
29             on_scope_end {
30 21     21   113514 _check_impl();
31             }
32 21         4945 }
33              
34             our @CHECK_LIST;
35             my %IMPL_CHECKED;
36             sub _check_impl {
37 21     21   115 while (my $data = shift @CHECK_LIST) {
38 12         55 my ($package, $interface_package, @fl) = @$data{qw/package interface_package filename line/};
39 12         90 assert_valid($package, $interface_package, @fl);
40              
41             # for Function::Interface::Types#ImplOf
42 12         226 $IMPL_CHECKED{$package}{$interface_package} = !!1;
43             }
44             }
45              
46             sub _register_check_list {
47 13     13   125 my ($package, $interface_package, $filename, $line) = @_;
48              
49 13         89 push @CHECK_LIST => +{
50             package => $package,
51             interface_package => $interface_package,
52             filename => $filename,
53             line => $line,
54             }
55             }
56              
57             sub assert_valid {
58 19     19 1 9778 my ($package, $interface_package, $filename, $line) = @_;
59 19         57 my @fl = ($filename, $line);
60              
61             {
62 19         77 my $ok = is_class_loaded($package);
63 19 100       1295 return _error("implements package is not loaded yet. required to use $package", @fl) if !$ok;
64             }
65              
66             {
67 19         41 my ($ok, $e) = try_load_class($interface_package);
  18         28  
  18         71  
68 18 100       2052 return _error("cannot load interface package: $e", @fl) if !$ok;
69             }
70              
71 17 100       66 my $iinfo = info_interface($interface_package)
72             or return _error("cannot get interface info", @fl);
73              
74 16         32 for my $ifunction_info (@{$iinfo->functions}) {
  16         56  
75 20         82 my $fname = $ifunction_info->subname;
76 20         77 my $def = $ifunction_info->definition;
77              
78 20 100       235 my $code = $package->can($fname)
79             or return _error("function `$fname` is required. Interface: $def", @fl);
80              
81 19 100       62 my $pinfo = info_params($code)
82             or return _error("cannot get function `$fname` parameters info. Interface: $def", @fl);
83 17 100       1606 my $rinfo = info_return($code)
84             or return _error("cannot get function `$fname` return info. Interface: $def", @fl);
85              
86 15 100       2423 check_params($pinfo, $ifunction_info)
87             or return _error("function `$fname` is invalid parameters. Interface: $def", @fl);
88 13 100       38 check_return($rinfo, $ifunction_info)
89             or return _error("function `$fname` is invalid return. Interface: $def", @fl);
90             }
91             }
92              
93             sub _error {
94 1     1   119 my ($msg, $filename, $line) = @_;
95 1         13 die sprintf "implements error: %s at %s line %s\n\tdied", $msg, $filename, $line;
96             }
97              
98             sub info_interface {
99 18     18 1 126 my $interface_package = shift;
100 18         96 Function::Interface::info($interface_package)
101             }
102              
103             sub info_params {
104 20     20 1 133 my $code = shift;
105 20         88 Function::Parameters::info($code)
106             }
107              
108             sub info_return {
109 18     18 1 118 my $code = shift;
110 18         67 Function::Return::info($code)
111             }
112              
113             sub check_params {
114 33     33 1 102 my ($pinfo, $ifunction_info) = @_;
115              
116 33 100       89 return unless $ifunction_info->keyword eq $pinfo->keyword;
117              
118 31         343 my $params_count = 0;
119 31         87 for my $key (qw/positional_required positional_optional named_required named_optional/) {
120 112         389 my @params = $pinfo->$key;
121 112         1440 $params_count += @params;
122              
123 112         163 for my $i (0 .. $#{$ifunction_info->$key}) {
  112         316  
124 13         43 my $ifp = $ifunction_info->$key->[$i];
125 13         23 my $p = $params[$i];
126 13 100       22 return unless check_param($p, $ifp);
127             }
128             }
129              
130 25 100       55 return unless $params_count == @{$ifunction_info->params};
  25         54  
131 22         83 return !!1
132             }
133              
134             sub check_param {
135 13     13 1 28 my ($param, $iparam) = @_;
136 13 100       73 return unless $param;
137 9   100     190 return $iparam->type eq $param->type
138             && $iparam->name eq $param->name
139             }
140              
141             sub check_return {
142 25     25 1 91 my ($rinfo, $ifunction_info) = @_;
143              
144 25 100       44 return unless @{$rinfo->types} == @{$ifunction_info->return};
  25         62  
  25         120  
145              
146 18         39 for my $i (0 .. $#{$ifunction_info->return}) {
  18         38  
147 8         54 my $ifr = $ifunction_info->return->[$i];
148 8         25 my $type = $rinfo->types->[$i];
149 8 100       34 return unless $ifr->type eq $type;
150             }
151 14         161 return !!1;
152             }
153              
154             sub impl_of {
155 14     14 1 9952 my ($package, $interface_package) = @_;
156 14 100       47 $package = ref $package ? blessed($package) : $package;
157 14         69 $IMPL_CHECKED{$package}{$interface_package}
158             }
159              
160             1;
161             __END__