Resolve RT#61076 (improve error messages)
[gitmo/Mouse.git] / lib / Mouse / Util / TypeConstraints.pm
CommitLineData
3b46bd49 1package Mouse::Util::TypeConstraints;
bc69ee88 2use Mouse::Util qw(does_role not_supported); # enables strict and warnings
9baf5d6b 3
718b5d9b 4use Carp ();
b7d1f970 5use Scalar::Util ();
6d28c5cf 6
684db121 7use Mouse::Meta::TypeConstraint;
bc69ee88 8use Mouse::Exporter;
9
10Mouse::Exporter->setup_import_methods(
11 as_is => [qw(
5d4810c1 12 as where message optimize_as
13 from via
ebe91068 14
15 type subtype class_type role_type duck_type
16 enum
17 coerce
18
bc69ee88 19 find_type_constraint
35ce550a 20 register_type_constraint
bc69ee88 21 )],
139d92d2 22);
23
718b5d9b 24our @CARP_NOT = qw(Mouse::Meta::Attribute);
25
cceb0e25 26my %TYPE;
4188b837 27
df448257 28# The root type
29$TYPE{Any} = Mouse::Meta::TypeConstraint->new(
30 name => 'Any',
31);
32
33my @builtins = (
34 # $name => $parent, $code,
35
36 # the base type
37 Item => 'Any', undef,
38
39 # the maybe[] type
40 Maybe => 'Item', undef,
41
42 # value types
43 Undef => 'Item', \&Undef,
44 Defined => 'Item', \&Defined,
45 Bool => 'Item', \&Bool,
46 Value => 'Defined', \&Value,
47 Str => 'Value', \&Str,
48 Num => 'Str', \&Num,
49 Int => 'Num', \&Int,
50
51 # ref types
52 Ref => 'Defined', \&Ref,
53 ScalarRef => 'Ref', \&ScalarRef,
54 ArrayRef => 'Ref', \&ArrayRef,
55 HashRef => 'Ref', \&HashRef,
56 CodeRef => 'Ref', \&CodeRef,
57 RegexpRef => 'Ref', \&RegexpRef,
58 GlobRef => 'Ref', \&GlobRef,
59
60 # object types
61 FileHandle => 'GlobRef', \&FileHandle,
62 Object => 'Ref', \&Object,
63
64 # special string types
65 ClassName => 'Str', \&ClassName,
66 RoleName => 'ClassName', \&RoleName,
67);
68
69
70while (my ($name, $parent, $code) = splice @builtins, 0, 3) {
71 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
72 name => $name,
73 parent => $TYPE{$parent},
74 optimized => $code,
75 );
76}
77
78# make it parametarizable
79
80$TYPE{Maybe} {constraint_generator} = \&_parameterize_Maybe_for;
81$TYPE{ArrayRef}{constraint_generator} = \&_parameterize_ArrayRef_for;
82$TYPE{HashRef} {constraint_generator} = \&_parameterize_HashRef_for;
83
84# sugars
85
a4b15169 86sub as ($) { (as => $_[0]) } ## no critic
87sub where (&) { (where => $_[0]) } ## no critic
88sub message (&) { (message => $_[0]) } ## no critic
89sub optimize_as (&) { (optimize_as => $_[0]) } ## no critic
61a02a3a 90
73766a27 91sub from { @_ }
a4b15169 92sub via (&) { $_[0] } ## no critic
61a02a3a 93
df448257 94# type utilities
993e62a7 95
df448257 96sub optimized_constraints { # DEPRECATED
97 Carp::cluck('optimized_constraints() has been deprecated');
98 return \%TYPE;
99}
d3982c7e 100
df448257 101undef @builtins; # free the allocated memory
102@builtins = keys %TYPE; # reuse it
103sub list_all_builtin_type_constraints { @builtins }
d4571def 104
df448257 105sub list_all_type_constraints { keys %TYPE }
993e62a7 106
f5ee065f 107sub _create_type{
108 my $mode = shift;
109
73766a27 110 my $name;
f5ee065f 111 my %args;
73766a27 112
f5ee065f 113 if(@_ == 1 && ref $_[0]){ # @_ : { name => $name, where => ... }
114 %args = %{$_[0]};
73766a27 115 }
f5ee065f 116 elsif(@_ == 2 && ref $_[1]){ # @_ : $name => { where => ... }
73766a27 117 $name = $_[0];
f5ee065f 118 %args = %{$_[1]};
73766a27 119 }
f5ee065f 120 elsif(@_ % 2){ # @_ : $name => ( where => ... )
121 ($name, %args) = @_;
73766a27 122 }
f5ee065f 123 else{ # @_ : (name => $name, where => ...)
124 %args = @_;
73766a27 125 }
126
f5ee065f 127 if(!defined $name){
4f24c598 128 $name = $args{name};
cd2b9201 129 }
d4571def 130
f5ee065f 131 $args{name} = $name;
b8434acc 132 my $parent;
133 if($mode eq 'subtype'){
134 $parent = delete $args{as};
135 if(!$parent){
136 $parent = delete $args{name};
4f24c598 137 $name = undef;
b8434acc 138 }
139 }
7dbebb1b 140
4f24c598 141 if(defined $name){
df448257 142 # set 'package_defined_in' only if it is not a core package
143 my $this = $args{package_defined_in};
144 if(!$this){
145 $this = caller(1);
146 if($this !~ /\A Mouse \b/xms){
147 $args{package_defined_in} = $this;
148 }
149 }
150
151 if($TYPE{$name}){
152 my $that = $TYPE{$name}->{package_defined_in} || __PACKAGE__;
b6f6f7b2 153 if($this ne $that) {
154 my $note = '';
155 if($that eq __PACKAGE__) {
156 $note = sprintf " ('%s' is %s type constraint)",
157 $name,
158 scalar(grep { $name eq $_ } list_all_builtin_type_constraints())
159 ? 'a builtin'
160 : 'an implicitly created';
161 }
162 Carp::croak("The type constraint '$name' has already been created in $that"
163 . " and cannot be created again in $this" . $note);
164 }
4f24c598 165 }
166 }
167 else{
168 $args{name} = '__ANON__';
f5ee065f 169 }
d4571def 170
b8434acc 171 $args{constraint} = delete $args{where} if exists $args{where};
24410e3a 172 $args{optimized} = delete $args{optimized_as} if exists $args{optimized_as};
0d9fea22 173
f5ee065f 174 my $constraint;
175 if($mode eq 'subtype'){
24410e3a 176 $constraint = find_or_create_isa_type_constraint($parent)->create_child_type(%args);
7a50b450 177 }
178 else{
f5ee065f 179 $constraint = Mouse::Meta::TypeConstraint->new(%args);
73766a27 180 }
7dbebb1b 181
4f24c598 182 if(defined $name){
183 return $TYPE{$name} = $constraint;
184 }
185 else{
186 return $constraint;
187 }
f5ee065f 188}
7dbebb1b 189
f5ee065f 190sub type {
191 return _create_type('type', @_);
192}
d4571def 193
f5ee065f 194sub subtype {
195 return _create_type('subtype', @_);
4188b837 196}
197
139d92d2 198sub coerce {
ffbbf459 199 my $type_name = shift;
61a02a3a 200
ffbbf459 201 my $type = find_type_constraint($type_name)
718b5d9b 202 or Carp::croak("Cannot find type '$type_name', perhaps you forgot to load it.");
61a02a3a 203
ffbbf459 204 $type->_add_type_coercions(@_);
cd2b9201 205 return;
4188b837 206}
207
139d92d2 208sub class_type {
337e3b0c 209 my($name, $options) = @_;
210 my $class = $options->{class} || $name;
df448257 211
212 # ClassType
337e3b0c 213 return _create_type 'subtype', $name => (
ddbad0b1 214 as => 'Object',
e3540312 215 optimized_as => Mouse::Util::generate_isa_predicate_for($class),
337e3b0c 216 );
ecc6e3b1 217}
218
139d92d2 219sub role_type {
337e3b0c 220 my($name, $options) = @_;
221 my $role = $options->{role} || $name;
df448257 222
223 # RoleType
337e3b0c 224 return _create_type 'subtype', $name => (
1d5ecd5f 225 as => 'Object',
b7d1f970 226 optimized_as => sub { Scalar::Util::blessed($_[0]) && does_role($_[0], $role) },
61a02a3a 227 );
47f36c05 228}
229
ebe91068 230sub duck_type {
231 my($name, @methods);
232
233 if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){
234 $name = shift;
235 }
236
237 @methods = (@_ == 1 && ref($_[0]) eq 'ARRAY') ? @{$_[0]} : @_;
238
df448257 239 # DuckType
188ff28f 240 return _create_type 'subtype', $name => (
241 as => 'Object',
ebe91068 242 optimized_as => Mouse::Util::generate_can_predicate_for(\@methods),
ebe91068 243 );
244}
245
d44f0d03 246sub enum {
f5ee065f 247 my($name, %valid);
248
f152b099 249 if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){
250 $name = shift;
f5ee065f 251 }
f152b099 252
253 %valid = map{ $_ => undef } (@_ == 1 && ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_);
254
df448257 255 # EnumType
188ff28f 256 return _create_type 'subtype', $name => (
257 as => 'Str',
f5ee065f 258 optimized_as => sub{ defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]} },
f5ee065f 259 );
260}
261
262sub _find_or_create_regular_type{
718b5d9b 263 my($spec, $create) = @_;
f5ee065f 264
265 return $TYPE{$spec} if exists $TYPE{$spec};
01904723 266
718b5d9b 267 my $meta = Mouse::Util::get_metaclass_by_name($spec);
268
269 if(!defined $meta){
270 return $create ? class_type($spec) : undef;
271 }
01904723 272
f48920c1 273 if(Mouse::Util::is_a_metarole($meta)){
1d5ecd5f 274 return role_type($spec);
f5ee065f 275 }
276 else{
1d5ecd5f 277 return class_type($spec);
f5ee065f 278 }
d44f0d03 279}
280
f5ee065f 281sub _find_or_create_parameterized_type{
282 my($base, $param) = @_;
283
284 my $name = sprintf '%s[%s]', $base->name, $param->name;
285
b4d791ba 286 $TYPE{$name} ||= $base->parameterize($param, $name);
f5ee065f 287}
b4d791ba 288
f5ee065f 289sub _find_or_create_union_type{
718b5d9b 290 return if grep{ not defined } @_;
a2f1294a 291 my @types = sort map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
f5ee065f 292
30b28db3 293 my $name = join '|', @types;
f5ee065f 294
df448257 295 # UnionType
b4d791ba 296 $TYPE{$name} ||= Mouse::Meta::TypeConstraint->new(
297 name => $name,
298 type_constraints => \@types,
b4d791ba 299 );
f5ee065f 300}
301
302# The type parser
f5ee065f 303
718b5d9b 304# param : '[' type ']' | NOTHING
305sub _parse_param {
306 my($c) = @_;
f5ee065f 307
718b5d9b 308 if($c->{spec} =~ s/^\[//){
309 my $type = _parse_type($c, 1);
f5ee065f 310
718b5d9b 311 if($c->{spec} =~ s/^\]//){
312 return $type;
321e5271 313 }
718b5d9b 314 Carp::croak("Syntax error in type: missing right square bracket in '$c->{orig}'");
315 }
57f0e313 316
718b5d9b 317 return undef;
318}
f5ee065f 319
718b5d9b 320# name : [\w.:]+
321sub _parse_name {
322 my($c, $create) = @_;
f5ee065f 323
718b5d9b 324 if($c->{spec} =~ s/\A ([\w.:]+) //xms){
325 return _find_or_create_regular_type($1, $create);
326 }
327 Carp::croak("Syntax error in type: expect type name near '$c->{spec}' in '$c->{orig}'");
328}
f5ee065f 329
718b5d9b 330# single_type : name param
331sub _parse_single_type {
332 my($c, $create) = @_;
f5ee065f 333
718b5d9b 334 my $type = _parse_name($c, $create);
335 my $param = _parse_param($c);
29376895 336
718b5d9b 337 if(defined $type){
338 if(defined $param){
339 return _find_or_create_parameterized_type($type, $param);
29376895 340 }
718b5d9b 341 else {
342 return $type;
29376895 343 }
f5ee065f 344 }
718b5d9b 345 elsif(defined $param){
346 Carp::croak("Undefined type with parameter [$param] in '$c->{orig}'");
993e62a7 347 }
348 else{
718b5d9b 349 return undef;
993e62a7 350 }
321e5271 351}
352
718b5d9b 353# type : single_type ('|' single_type)*
354sub _parse_type {
355 my($c, $create) = @_;
356
357 my $type = _parse_single_type($c, $create);
358 if($c->{spec}){ # can be an union type
359 my @types;
360 while($c->{spec} =~ s/^\|//){
361 push @types, _parse_single_type($c, $create);
362 }
363 if(@types){
364 return _find_or_create_union_type($type, @types);
365 }
366 }
367 return $type;
368}
369
f5ee065f 370
371sub find_type_constraint {
372 my($spec) = @_;
f48920c1 373 return $spec if Mouse::Util::is_a_type_constraint($spec);
df448257 374 return undef if !defined $spec;
f5ee065f 375
376 $spec =~ s/\s+//g;
377 return $TYPE{$spec};
2efc0af1 378}
379
35ce550a 380sub register_type_constraint {
381 my($constraint) = @_;
382 Carp::croak("No type supplied / type is not a valid type constraint")
383 unless Mouse::Util::is_a_type_constraint($constraint);
384 my $name = $constraint->name;
385 Carp::croak("can't register an unnamed type constraint")
386 unless defined $name;
387 return $TYPE{$name} = $constraint;
388}
389
f5ee065f 390sub find_or_parse_type_constraint {
391 my($spec) = @_;
f48920c1 392 return $spec if Mouse::Util::is_a_type_constraint($spec);
df448257 393 return undef if !defined $spec;
9c85e9dc 394
f5ee065f 395 $spec =~ s/\s+//g;
396 return $TYPE{$spec} || do{
718b5d9b 397 my $context = {
398 spec => $spec,
399 orig => $spec,
400 };
401 my $type = _parse_type($context);
402
403 if($context->{spec}){
404 Carp::croak("Syntax error: extra elements '$context->{spec}' in '$context->{orig}'");
405 }
f5ee065f 406 $type;
407 };
408}
321e5271 409
f5ee065f 410sub find_or_create_does_type_constraint{
ddbad0b1 411 # XXX: Moose does not register a new role_type, but Mouse does.
bddbe49f 412 return find_or_parse_type_constraint(@_) || role_type(@_);
f5ee065f 413}
414
415sub find_or_create_isa_type_constraint {
ddbad0b1 416 # XXX: Moose does not register a new class_type, but Mouse does.
f5ee065f 417 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 418}
419
d60c78b9 4201;
6feb83f1 421__END__
422
423=head1 NAME
424
5893ee36 425Mouse::Util::TypeConstraints - Type constraint system for Mouse
426
a25ca8d6 427=head1 VERSION
428
d88885bd 429This document describes Mouse version 0.64
a25ca8d6 430
5893ee36 431=head2 SYNOPSIS
432
433 use Mouse::Util::TypeConstraints;
434
435 subtype 'Natural'
436 => as 'Int'
437 => where { $_ > 0 };
438
439 subtype 'NaturalLessThanTen'
440 => as 'Natural'
441 => where { $_ < 10 }
442 => message { "This number ($_) is not less than ten!" };
443
444 coerce 'Num'
445 => from 'Str'
446 => via { 0+$_ };
447
448 enum 'RGBColors' => qw(red green blue);
449
450 no Mouse::Util::TypeConstraints;
451
452=head1 DESCRIPTION
453
454This module provides Mouse with the ability to create custom type
455constraints to be used in attribute definition.
456
457=head2 Important Caveat
458
459This is B<NOT> a type system for Perl 5. These are type constraints,
460and they are not used by Mouse unless you tell it to. No type
461inference is performed, expressions are not typed, etc. etc. etc.
462
463A type constraint is at heart a small "check if a value is valid"
464function. A constraint can be associated with an attribute. This
465simplifies parameter validation, and makes your code clearer to read,
466because you can refer to constraints by name.
467
468=head2 Slightly Less Important Caveat
469
470It is B<always> a good idea to quote your type names.
471
472This prevents Perl from trying to execute the call as an indirect
473object call. This can be an issue when you have a subtype with the
474same name as a valid class.
475
476For instance:
477
478 subtype DateTime => as Object => where { $_->isa('DateTime') };
479
480will I<just work>, while this:
481
482 use DateTime;
483 subtype DateTime => as Object => where { $_->isa('DateTime') };
484
485will fail silently and cause many headaches. The simple way to solve
486this, as well as future proof your subtypes from classes which have
487yet to have been created, is to quote the type name:
488
489 use DateTime;
490 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
491
492=head2 Default Type Constraints
493
494This module also provides a simple hierarchy for Perl 5 types, here is
495that hierarchy represented visually.
496
dba2e142 497 Any
5893ee36 498 Item
499 Bool
500 Maybe[`a]
501 Undef
502 Defined
503 Value
5893ee36 504 Str
dba2e142 505 Num
506 Int
507 ClassName
508 RoleName
5893ee36 509 Ref
510 ScalarRef
511 ArrayRef[`a]
512 HashRef[`a]
513 CodeRef
514 RegexpRef
515 GlobRef
dba2e142 516 FileHandle
5893ee36 517 Object
5893ee36 518
519B<NOTE:> Any type followed by a type parameter C<[`a]> can be
520parameterized, this means you can say:
521
522 ArrayRef[Int] # an array of integers
523 HashRef[CodeRef] # a hash of str to CODE ref mappings
524 Maybe[Str] # value may be a string, may be undefined
525
526If Mouse finds a name in brackets that it does not recognize as an
527existing type, it assumes that this is a class name, for example
528C<ArrayRef[DateTime]>.
529
5893ee36 530B<NOTE:> The C<Undef> type constraint for the most part works
531correctly now, but edge cases may still exist, please use it
532sparingly.
533
534B<NOTE:> The C<ClassName> type constraint does a complex package
535existence check. This means that your class B<must> be loaded for this
536type constraint to pass.
537
538B<NOTE:> The C<RoleName> constraint checks a string is a I<package
539name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
540constraint checks that an I<object does> the named role.
541
542=head2 Type Constraint Naming
543
544Type name declared via this module can only contain alphanumeric
545characters, colons (:), and periods (.).
546
547Since the types created by this module are global, it is suggested
548that you namespace your types just as you would namespace your
549modules. So instead of creating a I<Color> type for your
550B<My::Graphics> module, you would call the type
551I<My::Graphics::Types::Color> instead.
552
553=head2 Use with Other Constraint Modules
554
555This module can play nicely with other constraint modules with some
556slight tweaking. The C<where> clause in types is expected to be a
557C<CODE> reference which checks it's first argument and returns a
558boolean. Since most constraint modules work in a similar way, it
559should be simple to adapt them to work with Mouse.
560
561For instance, this is how you could use it with
562L<Declare::Constraints::Simple> to declare a completely new type.
563
564 type 'HashOfArrayOfObjects',
565 {
566 where => IsHashRef(
567 -keys => HasLength,
568 -values => IsArrayRef(IsObject)
569 )
570 };
571
572Here is an example of using L<Test::Deep> and it's non-test
573related C<eq_deeply> function.
574
575 type 'ArrayOfHashOfBarsAndRandomNumbers'
576 => where {
577 eq_deeply($_,
578 array_each(subhashof({
579 bar => isa('Bar'),
580 random_number => ignore()
581 })))
582 };
6feb83f1 583
584=head1 METHODS
585
24410e3a 586=head2 C<< list_all_builtin_type_constraints -> (Names) >>
6feb83f1 587
24410e3a 588Returns the names of builtin type constraints.
589
590=head2 C<< list_all_type_constraints -> (Names) >>
591
592Returns the names of all the type constraints.
6feb83f1 593
c91d12e0 594=head1 FUNCTIONS
595
596=over 4
597
c9cc6884 598=item C<< type $name => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 599
c9cc6884 600=item C<< subtype $name => as $parent => where { } ... -> Mouse::Meta::TypeConstraint >>
601
602=item C<< subtype as $parent => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 603
1820fffe 604=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 605
1820fffe 606=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 607
c9cc6884 608=item C<< duck_type($name, @methods | \@methods) -> Mouse::Meta::TypeConstraint >>
609
610=item C<< duck_type(\@methods) -> Mouse::Meta::TypeConstraint >>
611
612=item C<< enum($name, @values | \@values) -> Mouse::Meta::TypeConstraint >>
613
1820fffe 614=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
615
87f1f3d2 616=item C<< coerce $type => from $another_type, via { }, ... >>
617
1820fffe 618=back
619
620=over 4
621
622=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 623
624=back
625
5893ee36 626=head1 THANKS
627
1820fffe 628Much of this documentation was taken from C<Moose::Util::TypeConstraints>
629
630=head1 SEE ALSO
631
632L<Moose::Util::TypeConstraints>
5893ee36 633
6feb83f1 634=cut
635
636