tckect #42992: Method modifiers affect all classes in the whole inheritance tree
[gitmo/Mouse.git] / lib / Mouse / Util / TypeConstraints.pm
CommitLineData
3b46bd49 1package Mouse::Util::TypeConstraints;
d60c78b9 2use strict;
3use warnings;
139d92d2 4use base 'Exporter';
9baf5d6b 5
61a02a3a 6use Carp ();
6c169c50 7use Scalar::Util qw/blessed looks_like_number openhandle/;
684db121 8use Mouse::Meta::TypeConstraint;
d60c78b9 9
139d92d2 10our @EXPORT = qw(
d44f0d03 11 as where message from via type subtype coerce class_type role_type enum
ccf44227 12 find_type_constraint
139d92d2 13);
14
cceb0e25 15my %TYPE;
7dbebb1b 16my %TYPE_SOURCE;
8a7f2a8a 17my %COERCE;
18my %COERCE_KEYS;
4188b837 19
139d92d2 20sub as ($) {
61a02a3a 21 as => $_[0]
22}
139d92d2 23sub where (&) {
61a02a3a 24 where => $_[0]
25}
0f1dae9a 26sub message (&) {
61a02a3a 27 message => $_[0]
28}
29
139d92d2 30sub from { @_ }
31sub via (&) {
61a02a3a 32 $_[0]
33}
34
321e5271 35BEGIN {
381f326a 36 no warnings 'uninitialized';
cceb0e25 37 %TYPE = (
381f326a 38 Any => sub { 1 },
39 Item => sub { 1 },
40 Bool => sub {
c91d12e0 41 !defined($_[0]) || $_[0] eq "" || "$_[0]" eq '1' || "$_[0]" eq '0'
381f326a 42 },
c91d12e0 43 Undef => sub { !defined($_[0]) },
44 Defined => sub { defined($_[0]) },
45 Value => sub { defined($_[0]) && !ref($_[0]) },
46 Num => sub { !ref($_[0]) && looks_like_number($_[0]) },
47 Int => sub { defined($_[0]) && !ref($_[0]) && $_[0] =~ /^-?[0-9]+$/ },
48 Str => sub { defined($_[0]) && !ref($_[0]) },
49 ClassName => sub { Mouse::is_class_loaded($_[0]) },
50 Ref => sub { ref($_[0]) },
51
52 ScalarRef => sub { ref($_[0]) eq 'SCALAR' },
53 ArrayRef => sub { ref($_[0]) eq 'ARRAY' },
54 HashRef => sub { ref($_[0]) eq 'HASH' },
55 CodeRef => sub { ref($_[0]) eq 'CODE' },
56 RegexpRef => sub { ref($_[0]) eq 'Regexp' },
57 GlobRef => sub { ref($_[0]) eq 'GLOB' },
381f326a 58
59 FileHandle => sub {
c91d12e0 60 ref($_[0]) eq 'GLOB' && openhandle($_[0])
381f326a 61 or
c91d12e0 62 blessed($_[0]) && $_[0]->isa("IO::Handle")
abe4e887 63 },
381f326a 64
c91d12e0 65 Object => sub { blessed($_[0]) && blessed($_[0]) ne 'Regexp' },
8a7f2a8a 66 );
684db121 67 while (my ($name, $code) = each %TYPE) {
68 $TYPE{$name} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $name );
78b13827 69 }
d3982c7e 70
cceb0e25 71 sub optimized_constraints { \%TYPE }
72 my @TYPE_KEYS = keys %TYPE;
73 sub list_all_builtin_type_constraints { @TYPE_KEYS }
7dbebb1b 74
75 @TYPE_SOURCE{@TYPE_KEYS} = (__PACKAGE__) x @TYPE_KEYS;
381f326a 76}
d3982c7e 77
139d92d2 78sub type {
0d9fea22 79 my $pkg = caller(0);
80 my($name, %conf) = @_;
0d062abb 81 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
7dbebb1b 82 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
0d9fea22 83 };
321e5271 84 my $constraint = $conf{where} || do {
85 my $as = delete $conf{as} || 'Any';
86 if (! exists $TYPE{$as}) {
87 $TYPE{$as} = _build_type_constraint($as);
88 }
89 $TYPE{$as};
90 };
7dbebb1b 91
92 $TYPE_SOURCE{$name} = $pkg;
684db121 93 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
94 name => $name,
95 _compiled_type_constraint => sub {
96 local $_ = $_[0];
97 if (ref $constraint eq 'CODE') {
98 $constraint->($_[0])
99 } else {
100 $constraint->check($_[0])
101 }
102 }
103 );
0d9fea22 104}
105
139d92d2 106sub subtype {
4188b837 107 my $pkg = caller(0);
61a02a3a 108 my($name, %conf) = @_;
0d062abb 109 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
7dbebb1b 110 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
61a02a3a 111 };
321e5271 112 my $constraint = $conf{where};
113 my $as_constraint = find_or_create_isa_type_constraint($conf{as} || 'Any');
7dbebb1b 114
115 $TYPE_SOURCE{$name} = $pkg;
684db121 116 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
117 name => $name,
118 _compiled_type_constraint => (
119 $constraint ?
120 sub {
121 local $_ = $_[0];
122 $as_constraint->check($_[0]) && $constraint->($_[0])
123 } :
124 sub {
125 local $_ = $_[0];
126 $as_constraint->check($_[0]);
127 }
128 ),
129 );
7dbebb1b 130
d9f8c878 131 return $name;
4188b837 132}
133
139d92d2 134sub coerce {
61a02a3a 135 my($name, %conf) = @_;
136
137 Carp::croak "Cannot find type '$name', perhaps you forgot to load it."
cceb0e25 138 unless $TYPE{$name};
61a02a3a 139
8a7f2a8a 140 unless ($COERCE{$name}) {
141 $COERCE{$name} = {};
142 $COERCE_KEYS{$name} = [];
143 }
61a02a3a 144 while (my($type, $code) = each %conf) {
145 Carp::croak "A coercion action already exists for '$type'"
8a7f2a8a 146 if $COERCE{$name}->{$type};
61a02a3a 147
310ad28b 148 if (! $TYPE{$type}) {
149 # looks parameterized
150 if ($type =~ /^[^\[]+\[.+\]$/) {
94593ae8 151 $TYPE{$type} = _build_type_constraint($type);
310ad28b 152 } else {
153 Carp::croak "Could not find the type constraint ($type) to coerce from"
154 }
155 }
61a02a3a 156
daa8612f 157 unshift @{ $COERCE_KEYS{$name} }, $type;
8a7f2a8a 158 $COERCE{$name}->{$type} = $code;
61a02a3a 159 }
4188b837 160}
161
139d92d2 162sub class_type {
ecc6e3b1 163 my($name, $conf) = @_;
d9f8c878 164 if ($conf && $conf->{class}) {
165 # No, you're using this wrong
166 warn "class_type() should be class_type(ClassName). Perhaps you're looking for subtype $name => as '$conf->{class}'?";
167 subtype($name, as => $conf->{class});
168 } else {
169 subtype(
170 $name => where => sub { $_->isa($name) }
171 );
172 }
ecc6e3b1 173}
174
139d92d2 175sub role_type {
47f36c05 176 my($name, $conf) = @_;
177 my $role = $conf->{role};
139d92d2 178 subtype(
61a02a3a 179 $name => where => sub {
180 return unless defined $_ && ref($_) && $_->isa('Mouse::Object');
181 $_->meta->does_role($role);
182 }
183 );
47f36c05 184}
185
684db121 186# this is an original method for Mouse
4188b837 187sub typecast_constraints {
684db121 188 my($class, $pkg, $types, $value) = @_;
86b99892 189 Carp::croak("wrong arguments count") unless @_==4;
eec1bb49 190
b3b74cc6 191 local $_;
684db121 192 for my $type ( split /\|/, $types ) {
8a7f2a8a 193 next unless $COERCE{$type};
8a7f2a8a 194 for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
b3b74cc6 195 $_ = $value;
684db121 196 next unless $TYPE{$coerce_type}->check($value);
b3b74cc6 197 $_ = $value;
c91d12e0 198 $_ = $COERCE{$type}->{$coerce_type}->($value);
684db121 199 return $_ if $types->check($_);
4188b837 200 }
201 }
4188b837 202 return $value;
203}
204
01904723 205my $serial_enum = 0;
d44f0d03 206sub enum {
01904723 207 # enum ['small', 'medium', 'large']
208 if (ref($_[0]) eq 'ARRAY') {
209 my @elements = @{ shift @_ };
210
211 my $name = 'Mouse::Util::TypeConstaints::Enum::Serial::'
212 . ++$serial_enum;
213 enum($name, @elements);
214 return $name;
215 }
216
217 # enum size => 'small', 'medium', 'large'
d44f0d03 218 my $name = shift;
219 my %is_valid = map { $_ => 1 } @_;
220
221 subtype(
222 $name => where => sub { $is_valid{$_} }
223 );
224}
225
321e5271 226sub _build_type_constraint {
227
228 my $spec = shift;
229 my $code;
94593ae8 230 $spec =~ s/\s+//g;
321e5271 231 if ($spec =~ /^([^\[]+)\[(.+)\]$/) {
232 # parameterized
233 my $constraint = $1;
234 my $param = $2;
235 my $parent;
236 if ($constraint eq 'Maybe') {
237 $parent = _build_type_constraint('Undef');
238 } else {
239 $parent = _build_type_constraint($constraint);
240 }
241 my $child = _build_type_constraint($param);
242 if ($constraint eq 'ArrayRef') {
243 my $code_str =
244 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
245 "sub {\n" .
684db121 246 " if (\$parent->check(\$_[0])) {\n" .
321e5271 247 " foreach my \$e (\@{\$_[0]}) {\n" .
684db121 248 " return () unless \$child->check(\$e);\n" .
321e5271 249 " }\n" .
250 " return 1;\n" .
251 " }\n" .
252 " return ();\n" .
253 "};\n"
254 ;
255 $code = eval $code_str or Carp::confess("Failed to generate inline type constraint: $@");
256 } elsif ($constraint eq 'HashRef') {
257 my $code_str =
258 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
259 "sub {\n" .
684db121 260 " if (\$parent->check(\$_[0])) {\n" .
321e5271 261 " foreach my \$e (values \%{\$_[0]}) {\n" .
684db121 262 " return () unless \$child->check(\$e);\n" .
321e5271 263 " }\n" .
264 " return 1;\n" .
265 " }\n" .
266 " return ();\n" .
267 "};\n"
268 ;
269 $code = eval $code_str or Carp::confess($@);
270 } elsif ($constraint eq 'Maybe') {
271 my $code_str =
272 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
273 "sub {\n" .
684db121 274 " return \$child->check(\$_[0]) || \$parent->check(\$_[0]);\n" .
321e5271 275 "};\n"
276 ;
277 $code = eval $code_str or Carp::confess($@);
278 } else {
766534c2 279 Carp::confess("Support for parameterized types other than Maybe, ArrayRef or HashRef is not implemented yet");
321e5271 280 }
684db121 281 $TYPE{$spec} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
321e5271 282 } else {
283 $code = $TYPE{ $spec };
284 if (! $code) {
5c5a61e0 285 # is $spec a known role? If so, constrain with 'does' instead of 'isa'
286 require Mouse::Meta::Role;
287 my $check = Mouse::Meta::Role->_metaclass_cache($spec)?
288 'does' : 'isa';
321e5271 289 my $code_str =
290 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
291 "sub {\n" .
5c5a61e0 292 " Scalar::Util::blessed(\$_[0]) && \$_[0]->$check('$spec');\n" .
321e5271 293 "}"
294 ;
295 $code = eval $code_str or Carp::confess($@);
684db121 296 $TYPE{$spec} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
321e5271 297 }
298 }
684db121 299 return Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
321e5271 300}
301
302sub find_type_constraint {
303 my $type_constraint = shift;
304 return $TYPE{$type_constraint};
305}
306
307sub find_or_create_isa_type_constraint {
308 my $type_constraint = shift;
309
310 my $code;
311
312 $type_constraint =~ s/\s+//g;
94593ae8 313
314 $code = $TYPE{$type_constraint};
315 if (! $code) {
316 my @type_constraints = split /\|/, $type_constraint;
317 if (@type_constraints == 1) {
318 $code = $TYPE{$type_constraints[0]} ||
319 _build_type_constraint($type_constraints[0]);
320 } else {
321 my @code_list = map {
322 $TYPE{$_} || _build_type_constraint($_)
323 } @type_constraints;
684db121 324 $code = Mouse::Meta::TypeConstraint->new(
325 _compiled_type_constraint => sub {
326 my $i = 0;
327 for my $code (@code_list) {
328 return 1 if $code->check($_[0]);
329 }
330 return 0;
331 },
332 name => $type_constraint,
333 );
94593ae8 334 }
321e5271 335 }
336 return $code;
337}
338
d60c78b9 3391;
340
6feb83f1 341__END__
342
343=head1 NAME
344
5893ee36 345Mouse::Util::TypeConstraints - Type constraint system for Mouse
346
347=head2 SYNOPSIS
348
349 use Mouse::Util::TypeConstraints;
350
351 subtype 'Natural'
352 => as 'Int'
353 => where { $_ > 0 };
354
355 subtype 'NaturalLessThanTen'
356 => as 'Natural'
357 => where { $_ < 10 }
358 => message { "This number ($_) is not less than ten!" };
359
360 coerce 'Num'
361 => from 'Str'
362 => via { 0+$_ };
363
364 enum 'RGBColors' => qw(red green blue);
365
366 no Mouse::Util::TypeConstraints;
367
368=head1 DESCRIPTION
369
370This module provides Mouse with the ability to create custom type
371constraints to be used in attribute definition.
372
373=head2 Important Caveat
374
375This is B<NOT> a type system for Perl 5. These are type constraints,
376and they are not used by Mouse unless you tell it to. No type
377inference is performed, expressions are not typed, etc. etc. etc.
378
379A type constraint is at heart a small "check if a value is valid"
380function. A constraint can be associated with an attribute. This
381simplifies parameter validation, and makes your code clearer to read,
382because you can refer to constraints by name.
383
384=head2 Slightly Less Important Caveat
385
386It is B<always> a good idea to quote your type names.
387
388This prevents Perl from trying to execute the call as an indirect
389object call. This can be an issue when you have a subtype with the
390same name as a valid class.
391
392For instance:
393
394 subtype DateTime => as Object => where { $_->isa('DateTime') };
395
396will I<just work>, while this:
397
398 use DateTime;
399 subtype DateTime => as Object => where { $_->isa('DateTime') };
400
401will fail silently and cause many headaches. The simple way to solve
402this, as well as future proof your subtypes from classes which have
403yet to have been created, is to quote the type name:
404
405 use DateTime;
406 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
407
408=head2 Default Type Constraints
409
410This module also provides a simple hierarchy for Perl 5 types, here is
411that hierarchy represented visually.
412
413 Any
414 Item
415 Bool
416 Maybe[`a]
417 Undef
418 Defined
419 Value
420 Num
421 Int
422 Str
423 ClassName
424 RoleName
425 Ref
426 ScalarRef
427 ArrayRef[`a]
428 HashRef[`a]
429 CodeRef
430 RegexpRef
431 GlobRef
432 FileHandle
433 Object
434 Role
435
436B<NOTE:> Any type followed by a type parameter C<[`a]> can be
437parameterized, this means you can say:
438
439 ArrayRef[Int] # an array of integers
440 HashRef[CodeRef] # a hash of str to CODE ref mappings
441 Maybe[Str] # value may be a string, may be undefined
442
443If Mouse finds a name in brackets that it does not recognize as an
444existing type, it assumes that this is a class name, for example
445C<ArrayRef[DateTime]>.
446
447B<NOTE:> Unless you parameterize a type, then it is invalid to include
448the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
449name, I<not> as a parameterization of C<ArrayRef>.
450
451B<NOTE:> The C<Undef> type constraint for the most part works
452correctly now, but edge cases may still exist, please use it
453sparingly.
454
455B<NOTE:> The C<ClassName> type constraint does a complex package
456existence check. This means that your class B<must> be loaded for this
457type constraint to pass.
458
459B<NOTE:> The C<RoleName> constraint checks a string is a I<package
460name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
461constraint checks that an I<object does> the named role.
462
463=head2 Type Constraint Naming
464
465Type name declared via this module can only contain alphanumeric
466characters, colons (:), and periods (.).
467
468Since the types created by this module are global, it is suggested
469that you namespace your types just as you would namespace your
470modules. So instead of creating a I<Color> type for your
471B<My::Graphics> module, you would call the type
472I<My::Graphics::Types::Color> instead.
473
474=head2 Use with Other Constraint Modules
475
476This module can play nicely with other constraint modules with some
477slight tweaking. The C<where> clause in types is expected to be a
478C<CODE> reference which checks it's first argument and returns a
479boolean. Since most constraint modules work in a similar way, it
480should be simple to adapt them to work with Mouse.
481
482For instance, this is how you could use it with
483L<Declare::Constraints::Simple> to declare a completely new type.
484
485 type 'HashOfArrayOfObjects',
486 {
487 where => IsHashRef(
488 -keys => HasLength,
489 -values => IsArrayRef(IsObject)
490 )
491 };
492
493Here is an example of using L<Test::Deep> and it's non-test
494related C<eq_deeply> function.
495
496 type 'ArrayOfHashOfBarsAndRandomNumbers'
497 => where {
498 eq_deeply($_,
499 array_each(subhashof({
500 bar => isa('Bar'),
501 random_number => ignore()
502 })))
503 };
6feb83f1 504
505=head1 METHODS
506
507=head2 optimized_constraints -> HashRef[CODE]
508
509Returns the simple type constraints that Mouse understands.
510
c91d12e0 511=head1 FUNCTIONS
512
513=over 4
514
515=item B<subtype 'Name' => as 'Parent' => where { } ...>
516
517=item B<subtype as 'Parent' => where { } ...>
518
519=item B<class_type ($class, ?$options)>
520
521=item B<role_type ($role, ?$options)>
522
523=item B<enum (\@values)>
524
525=back
526
5893ee36 527=head1 THANKS
528
529Much of this documentation was taken from L<Moose::Util::TypeConstraints>
530
6feb83f1 531=cut
532
533