File Coverage

blib/lib/Devel/MAT/Tool/Future.pm
Criterion Covered Total %
statement 142 170 83.5
branch 65 108 60.1
condition 12 18 66.6
subroutine 24 28 85.7
pod 4 10 40.0
total 247 334 73.9


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2014-2022 -- leonerd@leonerd.org.uk
5              
6             package Devel::MAT::Tool::Future 0.03;
7              
8 3     3   50500598 use v5.14;
  3         19  
9 3     3   25 use warnings;
  3         7  
  3         364  
10 3     3   32 use base qw( Devel::MAT::Tool );
  3         9  
  3         1283  
11             Devel::MAT::Tool->VERSION( '0.49' );
12              
13 3     3   17370 use Carp;
  3         9  
  3         418  
14              
15 3     3   36 use Syntax::Keyword::Match;
  3         7  
  3         37  
16 3     3   2712 use String::Tagged;
  3         50692  
  3         186  
17              
18 3     3   25 use constant FOR_UI => 1;
  3         6  
  3         304  
19              
20 3     3   2009 use File::ShareDir qw( module_file );
  3         152046  
  3         11743  
21              
22             =head1 NAME
23              
24             C - analyse C logic
25              
26             =head1 DESCRIPTION
27              
28             This C tool performs analysis of objects and code logic built
29             using the L module.
30              
31             This version supports analysing code based on C version 0.24.
32              
33             =cut
34              
35             sub AUTOLOAD_TOOL
36             {
37 2     2 1 204907 shift;
38 2         10 my ( $pmat ) = @_;
39 2 50       7 return 1 if eval { $pmat->find_symbol( '%Future::' ) };
  2         32  
40             }
41              
42             sub init_tool
43             {
44 2     2 1 582 my $self = shift;
45              
46 2         29 my $df = $self->df;
47              
48 2         33 my $heap_total = scalar $df->heap;
49 2         37 my $count;
50              
51             # Are Futures XS or PP-based?
52             my $future_is_xs;
53 2 50       22 if( my $isaav = $self->pmat->find_symbol( '@Future::ISA' ) ) {
54 2         452 foreach my $isaelem ( $isaav->elems ) {
55 2         84 my $superclass = $isaelem->pv;
56 2 50       29 if( $superclass eq "Future::XS" ) {
    50          
57 0         0 $future_is_xs = 1;
58 0         0 last;
59             }
60             elsif( $superclass eq "Future::PP" ) {
61 2         6 $future_is_xs = 0;
62 2         7 last;
63             }
64             }
65             }
66 2 50       10 unless( defined $future_is_xs ) {
67             # TODO: Suppress this mesasge if $Future::VERSION is less than whatever it would need to be
68 0         0 warn "Unable to determine if Future is based on Future::XS or Future::PP; presuming older version on PP";
69             }
70              
71             # Find all the classes that derive from Future
72 2         10 $self->{classes} = \my %classes;
73 2         9 $classes{Future}++;
74              
75 2         6 $count = 0;
76 2         10 foreach my $sv ( $df->heap ) {
77 222193         1393648 $count++;
78 222193 100       425090 $self->report_progress( sprintf "Finding Future subclasses in %d of %d (%.2f%%)",
79             $count, $heap_total, 100*$count / $heap_total ) if ($count % 1000) == 0;
80              
81 222193 100       666515 next unless $sv->type eq "STASH";
82              
83             # populate the %classes hash
84 819         3569 $self->class_is_future( $sv );
85             }
86              
87 2         50551 my $persv_data = [ $future_is_xs ];
88              
89 2         22 $count = 0;
90 2         30 foreach my $sv ( $df->heap ) {
91 222193         2442210 $count++;
92 222193 100       432700 $self->report_progress( sprintf "Finding Future instances in %d of %d (%.2f%%)",
93             $count, $heap_total, 100*$count / $heap_total ) if ($count % 1000) == 0;
94              
95 222193 100       582535 next unless my $pkg = $sv->blessed;
96              
97 417 100       10353 $classes{ $pkg->stashname } and $sv->{tool_future} = $persv_data;
98             }
99              
100 2         22144 $self->init_cmd;
101             }
102              
103             sub init_cmd
104             {
105 2     2 0 10 my $self = shift;
106              
107             Devel::MAT::Tool::Show->register_extra(
108             sub {
109 4     4   13778 my ( $sv ) = @_;
110              
111 4 50       24 $sv->is_future or return undef;
112              
113 4         15 my $state = $sv->future_state;
114              
115 4         16 Devel::MAT::Cmd->printf( " %s state %s\n",
116             Devel::MAT::Cmd->format_symbol( "Future" ),
117             $state,
118             );
119              
120             match( $state : eq ) {
121             case( "done" ) {
122 1         11 my @result = $sv->future_result;
123 1         114 my @str;
124 1 50       6 push @str, "(empty)" if !@result;
125 1 50       28 push @str, Devel::MAT::Cmd->format_sv_with_value( $result[0] ) if @result;
126 1 50       110 push @str, "..." if @result > 1;
127              
128 1         4 Devel::MAT::Cmd->printf( " %s result: %s\n",
129             Devel::MAT::Cmd->format_symbol( "Future" ),
130             String::Tagged->join( ", ", @str ),
131             );
132             }
133 4 100       78 case( "failed" ) {
    100          
134 1         26 my @failure = $sv->future_failure;
135 1         74 my @str;
136 1 50       5 push @str, "(empty)" if !@failure;
137 1 50       4 if( @failure ) {
138 1 50       15 push @str, defined $failure[0]->pv
139             ? Devel::MAT::Cmd->format_value( $failure[0]->pv, pv => 1 )
140             : Devel::MAT::Cmd->format_sv( $failure[0] );
141 1 50       57 push @str, "..." if @failure > 1;
142             }
143              
144 1         5 Devel::MAT::Cmd->printf( " %s failure: %s\n",
145             Devel::MAT::Cmd->format_symbol( "Future" ),
146             String::Tagged->join( ", ", @str ),
147             );
148             }
149             }
150             }
151 2         74 );
152             }
153              
154             sub init_ui
155             {
156 0     0 1 0 my $self = shift;
157 0         0 my ( $ui ) = @_;
158              
159 0         0 foreach (qw( pending done failed cancelled )) {
160 0         0 $ui->register_icon( name => "future-$_", svg => module_file( __PACKAGE__, "icons/future-$_.svg" ) );
161             }
162              
163             $ui->provides_sv_detail(
164             type => "widget",
165             title => "Future",
166 0     0   0 render => sub { $self->render_sv_detail( @_ ) },
167 0         0 );
168             }
169              
170             =head1 METHODS
171              
172             =cut
173              
174             =head2 class_is_future
175              
176             $ok = $tool->class_is_future( $pkg );
177              
178             Returns true if the given package is a C class. C<$pkg> may be either
179             a C instance referring to a stash, or a plain string.
180              
181             =cut
182              
183             # TODO: This kind of logic might belong in Devel::MAT::SV itself
184              
185             sub class_is_future
186             {
187 1202     1202 1 2846 my $self = shift;
188 1202         2632 my ( $pkg ) = @_;
189 1202 100       6306 ref $pkg or $pkg = $self->{pmat}->find_symbol( "%${pkg}::" ); # stash
190              
191 1202   100     85602 return $self->{classes}{$pkg->stashname} //= $self->_class_is_future( $pkg );
192             }
193              
194             sub _class_is_future
195             {
196 817     817   10929 my $self = shift;
197 817         1584 my ( $pkg ) = @_;
198              
199 817 50       1975 return 1 if $pkg->stashname eq "Future";
200              
201 817 100       6613 my $isagv = $pkg->value( "ISA" ) or return 0;
202 373 50       12111 my $isaav = $isagv->array or return 0;
203              
204 373         8284 foreach my $superclass ( $isaav->elems ) {
205 379 100       12447 return 1 if $self->class_is_future( $superclass->pv );
206             }
207              
208 372         5177 return 0;
209             }
210              
211             =head1 SV METHODS
212              
213             This tool adds the following SV methods.
214              
215             =cut
216              
217             =head2 is_future (SV)
218              
219             $ok = $sv->is_future;
220              
221             Returns true if the C instance represents a C
222             instance.
223              
224             =cut
225              
226             sub Devel::MAT::SV::is_future
227             {
228 444578     444578 0 639448 my $sv = shift;
229              
230 444578         1323215 return defined $sv->{tool_future};
231             }
232              
233             sub Devel::MAT::SV::_future_is_xs
234             {
235 28     28   50 my $sv = shift;
236              
237 28   33     311 return $sv->{tool_future} && $sv->{tool_future}[0];
238             }
239              
240             sub Devel::MAT::SV::_future_xs_struct
241             {
242 0     0   0 my $sv = shift;
243              
244 0 0       0 $sv->basetype eq "SV" or return undef;
245              
246 0 0       0 my $ref = $sv->maybe_outref_named( "the FutureXS structure" ) or
247             croak "Expected $sv to have a FutureXS structure";
248 0         0 return $ref->sv;
249             }
250              
251             =head2 future_state (SV)
252              
253             $state = $sv->future_state;
254              
255             Returns a string describing the state of the given C instance; one of
256             C, C, C or C.
257              
258             =cut
259              
260             sub Devel::MAT::SV::future_state
261             {
262 24     24 0 1634 my $sv = shift;
263              
264 24 50       119 $sv->is_future or croak "$sv is not a Future";
265              
266 24 50       84 if( $sv->_future_is_xs ) {
267 0         0 my $struct = $sv->_future_xs_struct;
268              
269 0 0       0 if( $struct->field_named( "cancelled" ) ) {
    0          
    0          
270 0         0 return "cancelled";
271             }
272             elsif( $struct->maybe_field_named( "the failure AV" ) ) {
273 0         0 return "failed";
274             }
275             elsif( $struct->field_named( "ready" ) ) {
276 0         0 return "done";
277             }
278             else {
279 0         0 return "pending";
280             }
281             }
282             else {
283 24 50       175 $sv->type eq "HASH" or
284             croak "Expected $sv to be a HASH";
285              
286 24         43 my $tmp;
287 24 100 66     139 if( $tmp = $sv->value( "cancelled" ) and $tmp->uv ) {
    100 66        
    100          
288 6         307 return "cancelled";
289             }
290             elsif( $tmp = $sv->value( "failure" ) ) {
291 6         366 return "failed";
292             }
293             elsif( $tmp = $sv->value( "ready" ) and $tmp->uv ) {
294 6         373 return "done";
295             }
296             else {
297 6         491 return "pending";
298             }
299             }
300             }
301              
302             =head2 future_result
303              
304             @result = $sv->future_result;
305              
306             Returns a list of SVs containing the result of a successful C.
307              
308             =cut
309              
310             sub Devel::MAT::SV::future_result
311             {
312 2     2 0 4 my $sv = shift;
313              
314 2 50       5 $sv->is_future or croak "$sv is not a Future";
315              
316 2 50       6 if( $sv->_future_is_xs ) {
317 0         0 my $struct = $sv->_future_xs_struct;
318              
319 0         0 return $struct->field_named( "the result AV" )->elems;
320             }
321             else {
322 2 50       8 $sv->type eq "HASH" or
323             croak "Expected $sv to be a HASH";
324              
325 2         12 return $sv->value( "result" )->rv->elems;
326             }
327             }
328              
329             =head2 future_failure
330              
331             @failure = $sv->future_failure;
332              
333             Returns a list of SVs containing the failure of a failed C.
334              
335             =cut
336              
337             sub Devel::MAT::SV::future_failure
338             {
339 2     2 0 5 my $sv = shift;
340              
341 2 50       8 $sv->is_future or croak "$sv is not a Future";
342              
343 2 50       9 if( $sv->_future_is_xs ) {
344 0         0 my $struct = $sv->_future_xs_struct;
345              
346 0         0 return $struct->field_named( "the failure AV" )->elems;
347             }
348             else {
349 2 50       13 $sv->type eq "HASH" or
350             croak "Expected $sv to be a HASH";
351              
352 2         10 return $sv->value( "failure" )->rv->elems;
353             }
354             }
355              
356             sub render_sv_detail
357             {
358 0     0 0 0 my $self = shift;
359 0         0 my ( $sv ) = @_;
360              
361 0 0       0 $self->is_future( $sv ) or return undef;
362              
363 0         0 my $state = $self->future_state( $sv );
364              
365 0         0 return Devel::MAT::UI->make_table(
366             State => Devel::MAT::UI->make_widget_text_icon( ucfirst $state, "future-$state" ),
367             );
368             }
369              
370             =head1 EXTENSIONS TO FIND
371              
372             =cut
373              
374             package # hide
375             Devel::MAT::Tool::Find::filter::future;
376 3     3   41 use base qw( Devel::MAT::Tool::Find::filter );
  3         11  
  3         1692  
377              
378             =head2 find future
379              
380             pmat> find future -f
381             HASH(2)=Future at 0x55d43c854660: Future(failed) - SCALAR(PV) at 0x55d43c8546f0 = "It failed"
382              
383             Lists SVs that are Future instances, optionally matching only futures in a
384             given state.
385              
386             Takes the following named options
387              
388             =over 4
389              
390             =item --pending, -p
391              
392             Lists only Futures in the pending state
393              
394             =item --done, -d
395              
396             Lists only Futures in the done state
397              
398             =item --failed, -f
399              
400             Lists only Futures in the failed state
401              
402             =item --cancelled, -c
403              
404             Lists only Futures in the cancelled state
405              
406             =back
407              
408             =cut
409              
410 3     3   29 use constant FILTER_DESC => "Future instances";
  3         7  
  3         544  
411              
412 3         2554 use constant FILTER_OPTS => (
413             pending => { help => "only pending futures",
414             alias => "p" },
415             done => { help => "only done futures",
416             alias => "d" },
417             failed => { help => "only failed futures",
418             alias => "f" },
419             cancelled => { help => "only cancelled futures",
420             alias => "c" },
421 3     3   26 );
  3         7  
422              
423             sub build
424             {
425 4     4   7959 my $self = shift;
426 4         10 my $inv = shift;
427 4         12 my %opts = %{ +shift };
  4         22  
428              
429 4         11 my %only;
430 4   66     49 $opts{$_} and $only{$_}++ for qw( pending done failed cancelled );
431              
432             return sub {
433 444544     444544   3267016 my ( $sv ) = @_;
434              
435 444544 100       780577 return unless $sv->is_future;
436              
437 16         107 my $state = $sv->future_state;
438              
439 16 100 66     154 return if %only and !$only{$state};
440              
441 4         60 my $ret = String::Tagged->from_sprintf( "%s(%s)",
442             Devel::MAT::Cmd->format_symbol( "Future" ), # TODO: full class name of this instance?
443             Devel::MAT::Cmd->format_note( $state, 1 ),
444             );
445              
446             match( $state : eq ) {
447             case( "done" ) {
448 1         7 my @result = $sv->future_result;
449 1 50       78 $ret .= " - (empty)" if !@result;
450 1 50       8 $ret .= " - " . Devel::MAT::Cmd->format_sv_with_value( $result[0] ) if @result;
451 1 50       179 $ret .= ", ..." if @result > 1;
452             }
453 4 100       1822 case( "failed" ) {
    100          
454 1         9 my @failure = $sv->future_failure;
455 1 50       116 $ret .= " - (empty)" if !@failure;
456 1 50       7 if( @failure ) {
457 1 50       19 $ret .= " - " . ( defined $failure[0]->pv
458             ? Devel::MAT::Cmd->format_value( $failure[0]->pv, pv => 1 )
459             : Devel::MAT::Cmd->format_sv( $failure[0] ) );
460 1 50       116 $ret .= ", ..." if @failure > 1;
461             }
462             }
463             }
464              
465 4         41 return $ret;
466             }
467 4         57 }
468              
469             =head1 AUTHOR
470              
471             Paul Evans
472              
473             =cut
474              
475             0x55AA;