File Coverage

blib/lib/types.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package types;
2              
3 5     5   127338 use 5.008;
  5         19  
  5         169  
4 5     5   24 use strict;
  5         10  
  5         148  
5 5     5   20 use warnings;
  5         13  
  5         137  
6 5     5   6041 use optimize;
  0            
  0            
7              
8             our $VERSION;
9             $VERSION = "0.05_04";
10             # do { my @r = (q$Revision: 1.5 $ =~ /\d+/g); $r[0] = 0; sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker
11              
12             my %typed;
13             my %op_returns;
14             my %function_returns;
15             my %function_params;
16             use constant HINT_TYPES => 0x00000010; # a hopefully free HINT bit
17             use constant SVpad_TYPED => 0x40000000; # free SV flags bit. sync with B::CC
18             use B qw(OPpTARGET_MY OPf_MOD SVf_POK);
19             use B::Utils;
20              
21             our %const_map = (
22             "B::NV" => 'double',
23             "B::IV" => 'int',
24             "B::PV" => 'string',
25             );
26              
27             sub compare_type {
28             my($a,$b) = @_;
29             if ($a eq $b) {
30             return 1;
31             }
32             return 0;
33             }
34              
35             eval qq(sub B::NULL::name { "void" }) unless exists &B::NULL::name;
36             #use Data::Dumper;
37             sub check {
38             my $class = shift;
39             my $op = shift;
40             return unless $op;
41             # $DB::single = 1 if defined &DB::DB; # magic to allow debugging into CHECK blocks
42             my $cv = $op->find_cv();
43              
44             #return unless $^H & HINT_TYPES; # lexical blocks not yet
45             if (defined($optimize::state) and !($optimize::state->private & HINT_TYPES)) {
46             return;
47             }
48              
49             # if ($op->name eq 'padsv') {
50             # print $op->flags ."\n";
51             # }
52              
53             if (ref($op) eq 'B::PADOP' && $op->name eq 'gv') {
54             # $op->dump;
55             my $target = (($cv->PADLIST->ARRAY)[1]->ARRAY)[$op->padix];
56             # $target->dump;
57             # exit;
58             }
59              
60             if ($op->name eq 'int') {
61             $op_returns{$op->seq}->{type} = 'int';
62             $op_returns{$op->seq}->{name} = 'int()';
63             }
64              
65             if ($op->name eq 'padsv') {
66             my $target = (($cv->PADLIST->ARRAY)[0]->ARRAY)[$op->targ];
67             if ($target) {
68             if (UNIVERSAL::isa($target,'B::SV') && $target->FLAGS & SVpad_TYPED) {
69             $typed{$cv->ROOT->seq}->{$op->targ}->{type} = $target->SvSTASH->NAME;
70             $typed{$cv->ROOT->seq}->{$op->targ}->{name} = $target->PV;
71             } elsif (UNIVERSAL::isa($target,'B::SV') &&
72             exists($typed{$cv->ROOT->seq}->{$target->PV})) {
73             $typed{$cv->ROOT->seq}->{$op->targ} = $typed{$cv->ROOT->seq}->{$target->PV};
74             }
75             }
76             }
77             if ($cv->FLAGS & SVf_POK && !$function_params{$cv->START->seq}) {
78             #we have, we have, we have arguments
79             my @type;
80             my @name;
81             my $i = 1;
82             foreach (split ",", $cv->PV) {
83             my ($type, $sigil, $name) = split /\b/, $_;
84             # print "$type - $sigil - $name \n";
85             push @type, $type;
86             if ($sigil && $name) {
87             push @name, $sigil.$name;
88             $typed{$cv->ROOT->seq}->{"$sigil$name"}->{type} = $type;
89             $typed{$cv->ROOT->seq}->{"$sigil$name"}->{name} = $sigil.$name;
90             } else {
91             push @name, "Argument $i";
92             }
93             $i++;
94             }
95              
96             $function_params{$cv->START->seq}->{name} = \@name;
97             $function_params{$cv->START->seq}->{type} = \@type;
98              
99             #print $cv->PV . "\n";
100             $cv->PV(";@");
101              
102             }
103              
104             if (ref($op->next) ne 'B::NULL' &&
105             ($op->next->name =~/2cv$/ ||
106             ($op->next->name eq 'null' && $op->next->oldname =~/2cv$/))) {
107             my $entersub = $op->next;
108             my $i = 1;
109              
110             while ($entersub->name ne 'entersub' &&
111             ref($entersub->next) ne 'B::NULL') {
112             $i++ if ($entersub->name ne 'null');
113             $entersub = $entersub->next;
114              
115             }
116             if ($entersub->name eq 'entersub') {
117             my $sv;
118             if (ref($op) eq 'B::PADOP') {
119             $sv = (($cv->PADLIST->ARRAY)[1]->ARRAY)[$op->padix];
120             } else {
121             die;
122             }
123             if (ref($sv->CV) ne 'B::SPECIAL') {
124             my $foo = $sv->CV->START->seq;
125             if (exists($function_returns{$foo})) {
126             $op_returns{$op->seq + $i}->{type} = $function_returns{$foo}->{type};
127             $op_returns{$op->seq + $i}->{name} = $sv->STASH->NAME . "::" . $sv->SAFENAME."()";
128             # print "AND IT HAS A RETURN VALUE $i\n";
129             }
130             if (exists($function_params{$foo})) {
131             my $param_list = $entersub->first();
132             get_list_proto($param_list, $cv);
133             $param_list = delete($op_returns{$param_list->seq});
134             pop(@{$param_list->{type}});
135             pop(@{$param_list->{name}});
136             # print Data::Dumper::Dumper($function_params{$foo});
137             # print Data::Dumper::Dumper($param_list);
138             match_protos($function_params{$foo}, $param_list);
139             }
140             # $sv->CV->dump();
141             }
142             }
143             }
144              
145             sub match_protos {
146             my ($target, $source) = @_;
147             my $targets = scalar @{$target->{name}} - 1;
148             my $sources = scalar @{$source->{name}} - 1;
149              
150             if ($sources < $targets) {
151             die "Not enough items in list at " .
152             $optimize::state->file . ":" .
153             $optimize::state->line . "\n";
154             }
155             foreach my $i (0..$targets) {
156             my ($target_name, $target_type) =
157             ($target->{name}->[$i], $target->{type}->[$i]);
158             my ($source_name, $source_type) =
159             ($source->{name}->[$i], $source->{type}->[$i]);
160             if ((!$target_type->isa($source_type) and !$source_type->isa($target_type))
161             or ($target_type->can('check') && !$target_type->check($source_type)))
162             {
163             die "Type mismatch in list for" .
164             " $source_type ($source_name) to $target_type ($target_name) at " .
165             $optimize::state->file . ":" .
166             $optimize::state->line . "\n";
167             }
168              
169            
170             }
171             }
172              
173             if (0) {
174             if (ref($op->next) ne 'B::NULL') {
175             print $op->name . " -> " . $op->next->name . "\n";
176             }
177             if (ref($op->next) ne 'B::NULL' &&
178             ref($op->next->next) ne 'B::NULL' &&
179             $op->next->next->name eq 'entersub') {
180             print "sub entry\n";
181             }
182             }
183              
184             if (ref($op) eq 'B::LISTOP' && $op->first->name eq 'pushmark') {
185             get_list_proto($op,$cv);
186             }
187              
188             #print join(" ",ref($op->next), ref($cv->START), $op->next->name, $op->next->next->name),"\n";
189             if (ref($op->next) ne 'B::NULL' &&
190             ref($cv->START) ne 'B::NULL' &&
191             ($op->next->name eq 'lineseq' && $op->next->next->name =~/^leave/)
192             || $op->next->name eq 'return')
193             {
194             my ($type, $value, $const) = get_type($op, $cv);
195             my $lineseq = $op->next;
196             my $leave = $lineseq->next;
197              
198             if (exists($function_returns{$cv->START->seq}) &&
199             $function_returns{$cv->START->seq}->{type} ne $type) {
200             die "Return type mismatch: " . $op->name .
201             " $type at " .
202             $optimize::state->file . ":" .
203             $optimize::state->line . " does not match" .
204             " return value $function_returns{$cv->START->seq}->{type}".
205             " at $function_returns{$cv->START->seq}->{file}\n";
206             }
207            
208             my $subname = "";
209            
210             if (ref($cv->GV) ne 'B::SPECIAL' && $cv->GV->SAFENAME ne '__ANON__') {
211             $subname = $cv->GV->STASH->NAME . "::" . $cv->GV->SAFENAME;
212             }
213             if ($subname && exists($function_returns{$subname}) &&
214             $function_returns{$subname}->{type} ne $type) {
215             die "Function $subname redefined with a different type (was $function_returns{$subname}->{type} now $type) at " . $optimize::state->file . ":" . $optimize::state->line . "\n";
216             }
217            
218             $function_returns{$cv->START->seq}->{type} = $type;
219             $function_returns{$cv->START->seq}->{name} = $value;
220             $function_returns{$cv->START->seq}->{file} = $optimize::state->file . ":" . $optimize::state->line;
221              
222             if ($subname) {
223             $function_returns{$subname} = $function_returns{$cv->START->seq};
224             # print "GOT subname $subname\n";
225             }
226             # print "scope leave retval ($type, $value): " . $op->name . "-" . $lineseq->next->name . "\n";
227             }
228              
229             if (ref($op) eq 'B::BINOP') {
230            
231             my ($lhs, $rhs, $target, $expr, $const, $mod);
232             my ($lhs_v, $rhs_v, $target_v, $expr_v);
233              
234             if ($op->private & OPpTARGET_MY &&
235             exists($typed{$cv->ROOT->seq}->{$op->targ})) {
236             $target = $typed{$cv->ROOT->seq}->{$op->targ}->{type};
237             $target_v = $typed{$cv->ROOT->seq}->{$op->targ}->{name};
238             }
239              
240             if ($op->first->name eq 'padsv'
241             && exists($typed{$cv->ROOT->seq}->{$op->first->targ})) {
242             $rhs = $typed{$cv->ROOT->seq}->{$op->first->targ}->{type};
243             $rhs_v = $typed{$cv->ROOT->seq}->{$op->first->targ}->{name};
244             } elsif (exists($op_returns{$op->first->seq})) {
245             $rhs = $op_returns{$op->first->seq}->{type};
246             $rhs_v = $op_returns{$op->first->seq}->{name};
247             } elsif ($op->first->name eq 'const' &&
248             exists($const_map{ref($op->first->sv)})) {
249             $rhs = $const_map{ref($op->first->sv)};
250             $rhs_v = "constant '" . $op->first->sv->sv."'";
251             $const++;
252             } elsif ($op->first->name eq 'null' &&
253             $op->first->oldname eq 'list') {
254             get_list_proto($op->first,$cv);
255             }
256              
257             if ($op->last->name eq 'padsv'
258             && exists($typed{$cv->ROOT->seq}->{$op->last->targ})) {
259             $lhs = $typed{$cv->ROOT->seq}->{$op->last->targ}->{type};
260             $lhs_v = $typed{$cv->ROOT->seq}->{$op->last->targ}->{name};
261             if ($op->last->flags & OPf_MOD) {
262             die "target should be empty" if ($target);
263             $target = $lhs;
264             $target_v = $lhs_v;
265             $mod++;
266             }
267             } elsif (exists($op_returns{$op->last->seq})) {
268             $lhs = $op_returns{$op->last->seq}->{type};
269             $lhs_v = $op_returns{$op->last->seq}->{name};
270             } elsif ($op->last->name eq 'const' &&
271             exists($const_map{ref($op->last->sv)})) {
272             $lhs = $const_map{ref($op->last->sv)};
273             $lhs_v = "constant '" . $op->last->sv->sv."'";
274              
275             } elsif ($op->last->name eq 'null' &&
276             $op->last->oldname eq 'list') {
277             get_list_proto($op->first,$cv);
278             }
279              
280             $lhs_v = $lhs = "unknown" unless($lhs);
281             $rhs_v = $rhs = "unknown" unless($rhs);
282            
283             $target_v = $target = "" unless($target);
284              
285             return if ($target eq '' && $const);
286              
287             #first lets determine what the expression returns
288             # if they are equal the expression returns that
289             # otherwise it returns what is higher on he inclusion team
290             {
291             my($is_lhs, $is_rhs) = (0,0);
292             if ($lhs->can("check") && $lhs->check($rhs)) {
293             $is_lhs = 1;
294             } elsif ($lhs->isa($rhs)) {
295             $is_lhs = 1;
296             }
297              
298             if ($rhs->can("check") && $rhs->check($lhs)) {
299             $is_rhs = 1;
300             } elsif ($rhs->isa($lhs)) {
301             $is_rhs = 1;
302             }
303             if ($is_lhs && $is_rhs) {
304             $expr = $lhs;
305              
306             } elsif ($is_lhs) {
307             $expr = $lhs;
308             # print "$lhs < $rhs\n";
309             } elsif ($is_rhs) {
310             $expr = $rhs;
311             # print "$rhs < $lhs\n";
312             } else {
313             die "Type mismatch, can't " . $op->name .
314             " $rhs ($rhs_v) to $lhs ($lhs_v) at " .
315             $optimize::state->file . ":" .
316             $optimize::state->line . "\n";
317             }
318             $expr_v = "$lhs_v, $rhs_v";
319             # print "Expression returns ($expr) ($expr_v)" .
320             # $optimize::state->file . ": . " .
321             # $optimize::state->line . "\n";
322             }
323            
324              
325             # return if (!$lhs and $op->first->name eq 'const');
326            
327             #
328              
329             unless ($target) {
330             #the target is empty
331             $op_returns{$op->seq}->{type} = $expr;
332             $op_returns{$op->seq}->{name} = $expr_v;
333             return;
334             }
335            
336             # print "$expr - $target\n";
337             # print "$target->isa($expr): ". $target->isa($expr) . "\n";
338             # print "$expr->isa($target): ". $expr->isa($target) . "\n";
339              
340              
341              
342             if ( (!$target->isa($expr) and !$expr->isa($target))
343             or ($target->can('check') && !$target->check($expr)))
344             {
345             if ($mod) {
346             die "Type mismatch, can't " . $op->name .
347             " $rhs ($rhs_v) to $lhs ($lhs_v) at " .
348             $optimize::state->file . ":" .
349             $optimize::state->line . "\n";
350             } else {
351             die "Type mismatch, can't assign result of $lhs $lhs_v "
352             . $op->name . " $rhs $rhs_v to $target ($target_v) at "
353             . $optimize::state->file . ":"
354             . $optimize::state->line . "\n";
355             }
356             }
357             $op_returns{$op->seq}->{type} = $target;
358             $op_returns{$op->seq}->{name} = $target_v;
359             }
360              
361             }
362              
363             BEGIN { $optimize::loaded{"types"} = __PACKAGE__ }
364              
365             # block level types optimization not yet enabled
366             sub import {
367             my ($package, $filename, $line) = caller;
368             #$^H |= 0x00020000;
369             #$^H{"use_types"}++;
370             $^H |= HINT_TYPES;
371             optimize->register(\&check, $package, $filename, $line);
372             }
373              
374             sub unimport {
375             my ($package, $filename, $line) = caller;
376             $^H &= ~ HINT_TYPES;
377             #$^H |= 0x00020000;
378             #delete($^H{"use_types"});
379             optimize->unregister($package);
380             }
381              
382             sub get_type { # TODO also get attributes. see optimize
383             my($op, $cv) = @_;
384             my ($type, $value, $const) = ("","",0);
385             if ($op->name eq 'padsv'
386             && exists($typed{$cv->ROOT->seq}->{$op->targ})) {
387             $type = $typed{$cv->ROOT->seq}->{$op->targ}->{type};
388             $value = $typed{$cv->ROOT->seq}->{$op->targ}->{name};
389             } elsif (exists($op_returns{$op->seq})) {
390             $type = $op_returns{$op->seq}->{type};
391             $value = $op_returns{$op->seq}->{name};
392             } elsif ($op->name eq 'const' &&
393             exists($const_map{ref($op->sv)})) {
394             $type = $const_map{ref($op->sv)};
395             $value = "constant '" . $op->sv->sv."'";
396             $const++;
397             } elsif ($op->name eq 'null' &&
398             $op->oldname eq 'list') {
399             get_list_proto($op,$cv);
400             } else {
401             $type = $value = "unknown";
402             }
403             return ($type, $value, $const);
404             }
405              
406             sub get_list_proto {
407             my ($op, $cv) = @_;
408             my $o = $op->first->sibling();
409             # print "start\n";
410             my @type;
411             my @name;
412             while (ref($o) ne 'B::NULL') {
413             my $kid = $o;
414             if ($o->name eq 'null') {
415             $kid = $o->first;
416             }
417             if ($kid->name eq 'padsv' &&
418             exists($typed{$cv->ROOT->seq}->{$kid->targ})) {
419             push @type, $typed{$cv->ROOT->seq}->{$kid->targ}->{type};
420             push @name, $typed{$cv->ROOT->seq}->{$kid->targ}->{name};
421             } elsif (exists($op_returns{$kid->seq})) {
422             push @type, $op_returns{$kid->seq}->{type};
423             push @name, $op_returns{$kid->seq}->{name};
424             } elsif ($kid->name eq 'const' &&
425             exists($const_map{ref($kid->sv)})) {
426             push @type, $const_map{ref($kid->sv)};
427             push @name, $kid->sv->sv;
428             } else {
429             push @type, "unknown";
430             push @name, "unknown";
431             }
432             # print $kid->name . "\n";
433             $o = $o->sibling;
434             }
435              
436             if (@type > 1) {
437             $op_returns{$op->seq}->{type} = \@type;
438             $op_returns{$op->seq}->{name} = \@name;
439             } else {
440             $op_returns{$op->seq}->{type} = $type[0];
441             $op_returns{$op->seq}->{name} = $name[0];
442             }
443             # use Data::Dumper;
444             # print Dumper(\@type);
445             }
446              
447             package unknown;
448             our $dummy = 1;
449              
450             package int;
451             our $dummy = 1;
452             sub check {
453             return 0 if ($_[0] eq 'int' && ($_[1] ne 'number' && $_[1] ne 'int'));
454             return 1;
455             }
456             our $valid_attr = '^(int|double|string|unsigned|register|temporary|ro|readonly)$';
457             sub MODIFY_SCALAR_ATTRIBUTES {
458             my $pkg = shift;
459             my $v = shift;
460             my @bad;
461             my $attr = $valid_attr;
462             $attr =~ s/\b$pkg\b//;
463             if (@bad = grep !/$attr/, @_) { return @bad; }
464             else {
465             # XXX where to store the attributes?
466             no strict 'refs'; push @{"$pkg\::$v\::attributes"}, @_; # create a magic glob
467             return ();
468             }
469             }
470             sub FETCH_SCALAR_ATTRIBUTES {
471             no strict 'refs';
472             my $pkg = shift;
473             my $v = shift;
474             return @{"$pkg\::$v\::attributes"};
475             }
476              
477             package double;
478             use base qw(int);
479             sub check {
480             return 0 if ($_[1] eq 'string');
481             return 1;
482             }
483             our $dummy = 1;
484             *float = *double;
485              
486             package number;
487             use base qw(double);
488             sub check {
489             return 0 if ($_[1] eq 'string');
490             return 1;
491             }
492             our $dummy = 1;
493             package string;
494             use base qw(number);
495             sub check { return 1};
496             1;
497             __END__