File Coverage

blib/lib/Class/XSConstructor.pm
Criterion Covered Total %
statement 238 275 86.5
branch 157 216 72.6
condition 78 165 47.2
subroutine 27 29 93.1
pod 1 6 16.6
total 501 691 72.5


line stmt bran cond sub pod time code
1 25     25   3998991 use 5.008008;
  25         134  
2 25     25   159 use strict;
  25         74  
  25         1042  
3 25     25   183 use warnings;
  25         50  
  25         2614  
4              
5             package Class::XSConstructor;
6              
7 25     25   20674 use Exporter::Tiny 1.000000 qw( mkopt _croak _carp );
  25         155345  
  25         180  
8 25     25   19005 use List::Util 1.45 qw( uniq );
  25         514  
  25         5385  
9              
10             BEGIN {
11 25     25   103 our $AUTHORITY = 'cpan:TOBYINK';
12 25         54 our $VERSION = '0.023006';
13            
14 25 50       46 if ( eval { require Types::Standard; 1 } ) {
  25         23786  
  25         3836522  
15 25         312 Types::Standard->import(
16             qw/ is_ArrayRef is_HashRef is_ScalarRef is_CodeRef is_Object /
17             );
18             }
19             else {
20 0         0 eval q|
21             require Scalar::Util;
22             sub is_ArrayRef ($) { ref $_[0] eq 'ARRAY' }
23             sub is_HashRef ($) { ref $_[0] eq 'HASH' }
24             sub is_ScalarRef ($) { ref $_[0] eq 'SCALAR' or ref $_[0] eq 'REF' }
25             sub is_CodeRef ($) { ref $_[0] eq 'CODE' }
26             sub is_Object ($) { !!Scalar::Util::blessed($_[0]) }
27             |;
28             }
29            
30 25         103937 require XSLoader;
31 25         58574 __PACKAGE__->XSLoader::load( $VERSION );
32             };
33              
34             our ( %META, %BUILD_CACHE, %DEMOLISH_CACHE );
35              
36             sub import {
37 37     37   12210 my $class = shift;
38 37         118 my ( $package, $methodname );
39 37 100       187 if ( 'ARRAY' eq ref $_[0] ) {
40 1         3 ( $package, $methodname ) = @{+shift};
  1         4  
41             }
42 37   33     378 $package ||= our($SETUP_FOR) || caller;
      66        
43 37   100     211 $methodname ||= 'new';
44            
45 37   50     355 $META{$package} ||= { package => $package };
46            
47 37         186 my @XS_args = (
48             "$package\::$methodname",
49             "$package\::BUILDALL",
50             "$package\::XSCON_CLEAR_CONSTRUCTOR_CACHE",
51             );
52 37 50       119 if (our $REDEFINE) {
53 25     25   283 no warnings 'redefine';
  25         130  
  25         98207  
54 0         0 install_constructor( @XS_args );
55             }
56             else {
57 37         835 install_constructor( @XS_args );
58             }
59              
60 37         187 inheritance_stuff( $package );
61            
62 37         67 for my $pair (@{ mkopt \@_ }) {
  37         214  
63 72         1366 my ($name, $thing) = @$pair;
64 72         148 my %spec;
65             my $type;
66            
67 72 100       244 if ($name eq '!!') {
68 9         28 $META{$package}{strict_params} = !!1;
69 9         31 next;
70             }
71            
72 63 50 33     535 if ( is_ArrayRef $thing ) {
    100 66        
    100          
    100          
    50          
73 0         0 %spec = @$thing;
74             }
75             elsif ( is_HashRef $thing ) {
76 23         102 %spec = %$thing;
77             }
78             elsif ( is_Object $thing and $thing->can('compiled_check') || $thing->can('check') ) {
79 1         20 %spec = ( isa => $thing );
80             }
81             elsif ( is_CodeRef $thing ) {
82 1         3 %spec = ( isa => $thing );
83             }
84             elsif ( defined $thing ) {
85 0         0 _croak("What is %s???", $thing);
86             }
87            
88 63 100       295 if ( $name =~ /\A(.*)\!\z/ ) {
89 12         55 $name = $1;
90 12         42 $spec{required} = !!1;
91             }
92            
93 63 100 66     655 if ( is_Object $spec{isa} and $spec{isa}->can('compiled_check') ) {
    50 33        
94 1         12 $type = $spec{isa};
95 1         5 $spec{isa} = $type->compiled_check;
96             }
97             elsif ( is_Object $spec{isa} and $spec{isa}->can('check') ) {
98             # Support it for compatibility with more basic Type::API::Constraint
99             # implementations, but this will be slowwwwww!
100 0         0 $type = $spec{isa};
101 0     0   0 $spec{isa} = sub { !! $type->check($_[0]) };
  0         0  
102             }
103            
104 63 50 66     288 if ( defined $spec{coerce} and !ref $spec{coerce} and $spec{coerce} eq 1 ) {
      33        
105 0         0 my $c;
106 0 0 0     0 if (
    0 0        
      0        
      0        
107             $type->can('has_coercion')
108             and $type->has_coercion
109             and $type->can('coercion')
110             and is_Object( $c = $type->coercion )
111             and $c->can('compiled_coercion') ) {
112 0         0 $spec{coerce} = $c->compiled_coercion;
113             }
114             elsif ( $type->can('coerce') ) {
115 0     0   0 $spec{coerce} = sub { $type->coerce($_[0]) };
  0         0  
116             }
117             }
118            
119 63 50 66     254 if ( $spec{required} and exists $spec{init_arg} and not defined $spec{init_arg} ) {
      33        
120 0         0 _croak("Required attribute $name cannot have undef init_arg");
121             }
122            
123 63         376 my @unknown_keys = grep !/\A(isa|required|is|lazy|default|builder|coerce|init_arg|trigger|weak_ref|alias|slot_initializer|undef_tolerant|reader|clone|clone_on_write|clone_on_read)\z/, keys %spec;
124 63 50       185 if ( @unknown_keys ) {
125 0         0 _croak("Unknown keys in spec: %s", join ", ", sort @unknown_keys);
126             }
127            
128             my %meta_attribute = (
129             name => $name,
130             spec => \%spec,
131             flags => $class->_build_flags( $name, \%spec, $type ),
132             required => !!$spec{required},
133 63 100       270 init_arg => exists( $spec{init_arg} ) ? $spec{init_arg} : $name,
134             );
135            
136 63 100       286 if ( is_CodeRef $spec{isa} ) {
137 7         26 $meta_attribute{check} = $spec{isa};
138             }
139            
140 63 100 66     468 if ( is_CodeRef $spec{trigger} ) {
    100          
141 1         1 $meta_attribute{trigger} = $spec{trigger};
142             }
143             elsif ( defined $spec{trigger} and not ref $spec{trigger} ) {
144 2         4 $meta_attribute{trigger} = $spec{trigger};
145             }
146              
147 63 100       286 if ( is_CodeRef $spec{coerce} ) {
148 1         5 $meta_attribute{coercion} = $spec{coerce};
149             }
150            
151 63 100 100     356 if ( exists $spec{default} or defined $spec{builder} ) {
152             $meta_attribute{default} = $class->_canonicalize_defaults( \%spec )
153 8 50       41 unless $spec{lazy};
154             }
155            
156 63 100 66     283 if ( is_Object $type and $type->isa('Type::Tiny') ) {
157 1         19 $meta_attribute{type} = $type;
158             }
159            
160 63 50       344 if ( is_ArrayRef $spec{alias} ) {
    100          
161 0         0 $meta_attribute{aliases} = $spec{alias};
162             }
163             elsif ( $spec{alias} ) {
164 1         2 $meta_attribute{aliases} = [ $spec{alias} ];
165             }
166            
167 63 100       314 if ( is_CodeRef $spec{slot_initializer} ) {
168 1         3 $meta_attribute{slot_initializer} = $spec{slot_initializer};
169             }
170            
171 63 100       12728 if ( exists $spec{undef_tolerant} ) {
172 1         5 $meta_attribute{undef_tolerant} = !!$spec{undef_tolerant};
173             }
174            
175 63 100 66     320 if ( $spec{clone_on_write} or $spec{clone} ) {
176 2   33     11 $meta_attribute{clone_on_write} = $spec{clone_on_write} || $spec{clone};
177             }
178            
179             # Add new attribute
180 63   100     125 push @{ $META{$package}{params} ||= [] }, \%meta_attribute;
  63         508  
181             }
182            
183 37 100       288 if ( my $p = $META{$package}{params} ) {
184             # Dedupe by name, but keep *last* copy (reverse reverse!)
185 34         96 my %already;
186 34         121 @$p = reverse grep { not $already{$_->{name}}++ } reverse @$p;
  83         386  
187            
188 34 100       31751 if ( $META{$package}{strict_params} ) {
189             # Keep big list of all allowed init_args
190 9         34 %already = ();
191             $META{$package}{allow} = [
192             '__no_BUILD__',
193 21         17298 grep { not $already{$_}++ }
194             map {
195 9         42 my @names;
  21         309  
196 21 100       83 push @names, $_->{init_arg} if defined $_->{init_arg};
197 21 100       70 push @names, @{$_->{aliases}} if ref $_->{aliases};
  1         2  
198 21         93 @names;
199             } @$p
200             ];
201             }
202             }
203             else {
204 3         294 $META{$package}{params} = [];
205             }
206             }
207              
208             sub _canonicalize_defaults {
209 10     10   17 my $package = shift;
210 10         84 my $spec = shift;
211 10 100       59 if ( defined $spec->{builder} ) {
    100          
    100          
212 1         5 return \$spec->{builder};
213             }
214             elsif ( is_CodeRef $spec->{default} ) {
215 3         31 return $spec->{default};
216             }
217             elsif ( is_ScalarRef $spec->{default} ) {
218 2         5 my $str = ${ $spec->{default} };
  2         4  
219 2         267 return eval "sub { $str }";
220             }
221             else {
222 4         15 return $spec->{default};
223             }
224             }
225              
226             sub _is_bool ($) {
227 19     19   34 my $value = shift;
228 19 50       43 return !!0 unless defined $value;
229 19 100       61 return !!0 if ref $value;
230 9 50       62 return !!0 unless Scalar::Util::isdual( $value );
231 0 0 0     0 return !!1 if $value && "$value" eq '1' && $value+0 == 1;
      0        
232 0 0 0     0 return !!1 if !$value && "$value" eq q'' && $value+0 == 0;
      0        
233 0         0 return !!0;
234             }
235              
236             sub _created_as_number ($) {
237 21     21   35 my $value = shift;
238 21 50       63 return !!0 if utf8::is_utf8($value);
239 21 50       45 return !!0 unless defined $value;
240 21 100       64 return !!0 if ref $value;
241 11         50 require B;
242 11         52 my $b_obj = B::svref_2object(\$value);
243 11         56 my $flags = $b_obj->FLAGS;
244 11 100 66     108 return !!1 if $flags & ( B::SVp_IOK() | B::SVp_NOK() ) and !( $flags & B::SVp_POK() );
245 3         20 return !!0;
246             }
247              
248             sub _created_as_string ($) {
249 8     8   16 my $value = shift;
250 8 100 66     51 defined($value)
      66        
251             && !ref($value)
252             && !_is_bool($value)
253             && !_created_as_number($value);
254             }
255              
256             sub _type_to_number {
257 4     4   13 my ( $type, $no_recurse ) = @_;
258            
259 4 50 33     25 if ( is_Object $type and $type->isa('Type::Tiny') ) {
260 4         1501 require Types::Common;
261 4 50 33     479901 if ( $type == Types::Common::Any() or $type == Types::Common::Item() ) {
    50 0        
    50 0        
    50 0        
    100          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    0          
262 0         0 return XSCON_TYPE_BASE_ANY;
263             }
264             elsif ( $type == Types::Common::Defined() ) {
265 0         0 return XSCON_TYPE_BASE_DEFINED;
266             }
267             elsif ( $type == Types::Common::Ref() ) {
268 0         0 return XSCON_TYPE_BASE_REF;
269             }
270             elsif ( $type == Types::Common::Bool() ) {
271 0         0 return XSCON_TYPE_BASE_BOOL;
272             }
273             elsif ( $type == Types::Common::Int() ) {
274 3         23192 return XSCON_TYPE_BASE_INT;
275             }
276             elsif ( $type == Types::Common::PositiveOrZeroInt() ) {
277 0         0 return XSCON_TYPE_BASE_PZINT;
278             }
279             elsif ( $type == Types::Common::Num() ) {
280 0         0 return XSCON_TYPE_BASE_NUM;
281             }
282             elsif ( $type == Types::Common::PositiveOrZeroNum() ) {
283 0         0 return XSCON_TYPE_BASE_PZNUM;
284             }
285             elsif ( $type == Types::Common::Str() ) {
286 0         0 return XSCON_TYPE_BASE_STR;
287             }
288             elsif ( $type == Types::Common::NonEmptyStr() ) {
289 0         0 return XSCON_TYPE_BASE_NESTR;
290             }
291             elsif ( $type == Types::Common::ClassName() ) {
292 0         0 return XSCON_TYPE_BASE_CLASSNAME;
293             }
294             elsif ( $type == Types::Common::Object() ) {
295 0         0 return XSCON_TYPE_BASE_OBJECT;
296             }
297             elsif ( $type == Types::Common::ScalarRef() ) {
298 0         0 return XSCON_TYPE_BASE_SCALARREF;
299             }
300             elsif ( $type == Types::Common::CodeRef() ) {
301 0         0 return XSCON_TYPE_BASE_CODEREF;
302             }
303             elsif ( $type == Types::Common::ArrayRef() ) {
304 0         0 return XSCON_TYPE_ARRAYREF;
305             }
306             elsif ( $type == Types::Common::HashRef() ) {
307 1         24337 return XSCON_TYPE_HASHREF;
308             }
309 0         0 elsif ( $type->is_parameterized and @{ $type->parameters } == 1 and (
310             $type->parameterized_from == Types::Common::ArrayRef()
311             or $type->parameterized_from == Types::Common::HashRef()
312             ) ) {
313 0 0       0 return _type_to_number( $type->parameterized_from, 1 ) | _type_to_number( $type->type_parameter, 1 ) unless $no_recurse;
314             }
315             }
316            
317             # Returning 15 indicates an unknown type.
318             # 31 will be an arrayref of some unknown type.
319             # 47 will be an hashref of some unknown type.
320             # Class::XSAccessor won't be able to do the type check internally.
321 0         0 return XSCON_TYPE_OTHER;
322             }
323              
324             sub _build_flags {
325 63     63   123 my $package = shift;
326 63         107 my $name = shift;
327 63         132 my $spec = shift;
328 63         126 my $type = shift;
329            
330 63         122 my $flags = 0;
331 63 100       208 $flags |= XSCON_FLAG_REQUIRED if $spec->{required};
332 63 100       287 $flags |= XSCON_FLAG_HAS_TYPE_CONSTRAINT if is_CodeRef $spec->{isa};
333 63 100       306 $flags |= XSCON_FLAG_HAS_TYPE_COERCION if is_CodeRef $spec->{coerce};
334 63 100 100     318 $flags |= XSCON_FLAG_NO_INIT_ARG if exists($spec->{init_arg}) && !defined($spec->{init_arg});
335 63 100 66     362 $flags |= XSCON_FLAG_HAS_INIT_ARG if defined($spec->{init_arg}) && ( $spec->{init_arg} ne $name );
336 63 100       269 $flags |= XSCON_FLAG_HAS_TRIGGER if $spec->{trigger};
337 63 100       245 $flags |= XSCON_FLAG_WEAKEN if $spec->{weak_ref};
338 63 100       198 $flags |= XSCON_FLAG_HAS_ALIASES if $spec->{alias};
339 63 100       159 $flags |= XSCON_FLAG_HAS_SLOT_INITIALIZER if $spec->{slot_initializer};
340 63 100       178 $flags |= XSCON_FLAG_UNDEF_TOLERANT if $spec->{undef_tolerant};
341 63 100 66     340 $flags |= XSCON_FLAG_CLONE_ON_WRITE if $spec->{clone_on_write} || $spec->{clone};
342            
343 63 50       184 unless ( $spec->{lazy} ) {
344             $flags |= XSCON_FLAG_HAS_DEFAULT
345             if exists($spec->{default})
346 63 100 100     340 || defined($spec->{builder});
347             $flags |= ( _common_default($spec->{default}) << +XSCON_BITSHIFT_DEFAULTS )
348 63 100       201 if exists($spec->{default});
349             }
350            
351 63 100       322 if ( $type ) {
    100          
352 1         16 my $has_common_type = _type_to_number( $type );
353 1         5 $flags |= ( $has_common_type << +XSCON_BITSHIFT_TYPES );
354             }
355             elsif ( is_CodeRef $spec->{isa} ) {
356 6         15 $flags |= ( 15 << +XSCON_BITSHIFT_TYPES );
357             }
358            
359 63         615 return $flags;
360             }
361              
362             sub _common_default {
363 9 50   9   24 die unless @_ == 1;
364 9         25 my $default = shift;
365            
366 9 50       31 return XSCON_DEFAULT_UNDEF if ( not defined $default );
367 9 50 66     30 return XSCON_DEFAULT_ZERO if ( _created_as_number $default and $default == 0 );
368 9 100 100     25 return XSCON_DEFAULT_ONE if ( _created_as_number $default and $default == 1 );
369 8 50 33     22 return XSCON_DEFAULT_FALSE if ( _is_bool $default and not $default );
370 8 50 33     28 return XSCON_DEFAULT_TRUE if ( _is_bool $default and $default );
371 8 50 66     54 return XSCON_DEFAULT_EMPTY_STR if ( _created_as_string $default and $default eq '' );
372 8 100 100     41 return XSCON_DEFAULT_EMPTY_ARRAY if ( is_ScalarRef $default and $$default eq '[]' );
373 7 100 66     32 return XSCON_DEFAULT_EMPTY_HASH if ( is_ScalarRef $default and $$default eq '{}' );
374            
375 6         29 return 0;
376             }
377              
378             sub inheritance_stuff {
379 37     37 0 73 my $package = shift;
380            
381 37 50       2685 require( $] >= 5.010 ? "mro.pm" : "MRO/Compat.pm" );
382            
383 37         2890 my @isa = @{ mro::get_linear_isa($package) };
  37         365  
384 37         96 shift @isa; # discard $package itself
385 37 100       187 return unless @isa;
386            
387 9         34 for my $parent ( @isa ) {
388 25     25   262 no strict 'refs';
  25         58  
  25         1471  
389 25     25   163 no warnings 'once';
  25         69  
  25         11727  
390             # Moo will sometimes not have a constructor in &{"${parent}::new"}
391             # when by all that is good and holy, it should.
392 9 50 66     46 if ( $INC{'Moo.pm'} and $Moo::MAKERS{$parent} ) {
393 1         10 Moo->_constructor_maker_for( $parent )->install_delayed;
394 1         31275 Sub::Defer::undefer_sub( \&{"${parent}::new"} );
  1         5  
395             }
396 9 50       2625 if ( defined &{"${parent}::new"} ) {
  9         58  
397 9 100       34 if ( not $META{$parent} ) {
398             # We are inheriting from a foreign class.
399 3         9 $META{$package}{foreignclass} = $parent;
400 3         5 $META{$package}{foreignconstructor} = \&{"${parent}::new"};
  3         12  
401 3         33 $META{$package}{foreignbuildall} = $parent->can('BUILDALL');
402             }
403 9         27 last;
404             }
405             }
406            
407 9         17 my @attrs;
408 9         26 for my $parent ( reverse @isa ) {
409 10 100       41 my $p_attrs = $META{$parent}{params} or next;
410 6         21 push @attrs, @$p_attrs;
411             }
412            
413 9         32 $META{$package}{params} = \@attrs;
414             }
415              
416             sub populate_demolish {
417 2   33 2 0 11 my $package = ref($_[0]) || $_[0];
418 2   33     8 my $klass = ref($_[1]) || $_[1];
419            
420 2 50       15 if (!$klass->can('DEMOLISH')) {
421 0         0 $DEMOLISH_CACHE{$klass} = 0;
422 0         0 return;
423             }
424            
425 2 50       33 require( $] >= 5.010 ? "mro.pm" : "MRO/Compat.pm" );
426 25     25   203 no strict 'refs';
  25         59  
  25         1245  
427 25     25   149 no warnings 'once';
  25         60  
  25         7053  
428            
429             $DEMOLISH_CACHE{$klass} = [
430             reverse
431 3 50       6 map { ( *{$_}{CODE} ) ? ( *{$_}{CODE} ) : () }
  3         11  
  3         24  
432 3         9 map { "$_\::DEMOLISH" }
433 2         5 reverse @{ mro::get_linear_isa($klass) }
  2         10  
434             ];
435            
436 2         9 return;
437             }
438              
439             sub populate_build {
440 35   33 35 0 225 my $package = ref($_[0]) || $_[0];
441 35   33     193 my $klass = ref($_[1]) || $_[1];
442            
443 35 100       308 if (!$klass->can('BUILD')) {
444 27         83 $BUILD_CACHE{$klass} = 0;
445 27         114 return;
446             }
447            
448 8 50       99 require( $] >= 5.010 ? "mro.pm" : "MRO/Compat.pm" );
449 25     25   215 no strict 'refs';
  25         99  
  25         1141  
450 25     25   134 no warnings 'once';
  25         206  
  25         11065  
451            
452             $BUILD_CACHE{$klass} = [
453 14 100       21 map { ( *{$_}{CODE} ) ? ( *{$_}{CODE} ) : () }
  14         65  
  13         57  
454 14         47 map { "$_\::BUILD" }
455 8         22 reverse @{ mro::get_linear_isa($klass) }
  8         56  
456             ];
457            
458 8         26 return;
459             }
460              
461             sub get_metadata {
462 34   33 34 1 6310644 my $klass = ref($_[0]) || $_[0];
463 34 50       228 my $meta = $META{$klass} or return;
464            
465             $meta->{buildargs} ||= $klass->can('BUILDARGS')
466 34 50 66     780 unless $meta->{has_standard_buildargs};
467            
468             $meta->{foreignbuildargs} ||= $klass->can('FOREIGNBUILDARGS')
469 34 100 66     225 if $meta->{foreignconstructor} && !$meta->{foreignbuildall};
      100        
470            
471 34         314 return $meta;
472             }
473              
474             sub get_build_methods {
475 33   33 33 0 284 my $klass = ref($_[0]) || $_[0];
476 33         242 __PACKAGE__->populate_build( $klass );
477 33 100       72 return @{ $BUILD_CACHE{$klass} or [] };
  33         1457  
478             }
479              
480             sub get_demolish_methods {
481 1   33 1 0 2645 my $klass = ref($_[0]) || $_[0];
482 1         7 __PACKAGE__->populate_demolish( $klass );
483 1 50       2 return @{ $DEMOLISH_CACHE{$klass} or [] };
  1         32  
484             }
485              
486             __PACKAGE__
487             __END__