Moose's make_immutable returns true allowing calling code to skip
[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/;
d60c78b9 8
139d92d2 9our @EXPORT = qw(
d44f0d03 10 as where message from via type subtype coerce class_type role_type enum
139d92d2 11);
12
cceb0e25 13my %TYPE;
7dbebb1b 14my %TYPE_SOURCE;
8a7f2a8a 15my %COERCE;
16my %COERCE_KEYS;
4188b837 17
139d92d2 18sub as ($) {
61a02a3a 19 as => $_[0]
20}
139d92d2 21sub where (&) {
61a02a3a 22 where => $_[0]
23}
0f1dae9a 24sub message (&) {
61a02a3a 25 message => $_[0]
26}
27
139d92d2 28sub from { @_ }
29sub via (&) {
61a02a3a 30 $_[0]
31}
32
381f326a 33my $optimized_constraints;
34my $optimized_constraints_base;
35{
36 no warnings 'uninitialized';
cceb0e25 37 %TYPE = (
381f326a 38 Any => sub { 1 },
39 Item => sub { 1 },
40 Bool => sub {
41 !defined($_) || $_ eq "" || "$_" eq '1' || "$_" eq '0'
42 },
43 Undef => sub { !defined($_) },
44 Defined => sub { defined($_) },
45 Value => sub { defined($_) && !ref($_) },
46 Num => sub { !ref($_) && looks_like_number($_) },
47 Int => sub { defined($_) && !ref($_) && /^-?[0-9]+$/ },
48 Str => sub { defined($_) && !ref($_) },
49 ClassName => sub { Mouse::is_class_loaded($_) },
50 Ref => sub { ref($_) },
51
52 ScalarRef => sub { ref($_) eq 'SCALAR' },
53 ArrayRef => sub { ref($_) eq 'ARRAY' },
54 HashRef => sub { ref($_) eq 'HASH' },
55 CodeRef => sub { ref($_) eq 'CODE' },
56 RegexpRef => sub { ref($_) eq 'Regexp' },
57 GlobRef => sub { ref($_) eq 'GLOB' },
58
59 FileHandle => sub {
abe4e887 60 ref($_) eq 'GLOB' && openhandle($_)
381f326a 61 or
abe4e887 62 blessed($_) && $_->isa("IO::Handle")
63 },
381f326a 64
65 Object => sub { blessed($_) && blessed($_) ne 'Regexp' },
8a7f2a8a 66 );
d3982c7e 67
cceb0e25 68 sub optimized_constraints { \%TYPE }
69 my @TYPE_KEYS = keys %TYPE;
70 sub list_all_builtin_type_constraints { @TYPE_KEYS }
7dbebb1b 71
72 @TYPE_SOURCE{@TYPE_KEYS} = (__PACKAGE__) x @TYPE_KEYS;
381f326a 73}
d3982c7e 74
139d92d2 75sub type {
0d9fea22 76 my $pkg = caller(0);
77 my($name, %conf) = @_;
0d062abb 78 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
7dbebb1b 79 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
0d9fea22 80 };
7dbebb1b 81 my $constraint = $conf{where} || do { $TYPE{delete $conf{as} || 'Any' } };
82
83 $TYPE_SOURCE{$name} = $pkg;
84 $TYPE{$name} = $constraint;
0d9fea22 85}
86
139d92d2 87sub subtype {
4188b837 88 my $pkg = caller(0);
61a02a3a 89 my($name, %conf) = @_;
0d062abb 90 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
7dbebb1b 91 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
61a02a3a 92 };
7dbebb1b 93 my $constraint = $conf{where} || do { $TYPE{delete $conf{as} || 'Any' } };
94 my $as = $conf{as} || '';
95
96 $TYPE_SOURCE{$name} = $pkg;
97
cceb0e25 98 if ($as = $TYPE{$as}) {
7dbebb1b 99 $TYPE{$name} = sub { $as->($_) && $constraint->($_) };
1fbefea5 100 } else {
7dbebb1b 101 $TYPE{$name} = $constraint;
1fbefea5 102 }
4188b837 103}
104
139d92d2 105sub coerce {
61a02a3a 106 my($name, %conf) = @_;
107
108 Carp::croak "Cannot find type '$name', perhaps you forgot to load it."
cceb0e25 109 unless $TYPE{$name};
61a02a3a 110
8a7f2a8a 111 unless ($COERCE{$name}) {
112 $COERCE{$name} = {};
113 $COERCE_KEYS{$name} = [];
114 }
61a02a3a 115 while (my($type, $code) = each %conf) {
116 Carp::croak "A coercion action already exists for '$type'"
8a7f2a8a 117 if $COERCE{$name}->{$type};
61a02a3a 118
119 Carp::croak "Could not find the type constraint ($type) to coerce from"
cceb0e25 120 unless $TYPE{$type};
61a02a3a 121
8a7f2a8a 122 push @{ $COERCE_KEYS{$name} }, $type;
123 $COERCE{$name}->{$type} = $code;
61a02a3a 124 }
4188b837 125}
126
139d92d2 127sub class_type {
ecc6e3b1 128 my $pkg = caller(0);
ecc6e3b1 129 my($name, $conf) = @_;
130 my $class = $conf->{class};
139d92d2 131 subtype(
4a957f05 132 $name => where => sub { $_->isa($class) }
61a02a3a 133 );
ecc6e3b1 134}
135
139d92d2 136sub role_type {
47f36c05 137 my($name, $conf) = @_;
138 my $role = $conf->{role};
139d92d2 139 subtype(
61a02a3a 140 $name => where => sub {
141 return unless defined $_ && ref($_) && $_->isa('Mouse::Object');
142 $_->meta->does_role($role);
143 }
144 );
47f36c05 145}
146
4188b837 147sub typecast_constraints {
eec1bb49 148 my($class, $pkg, $type_constraint, $types, $value) = @_;
eec1bb49 149
b3b74cc6 150 local $_;
eec1bb49 151 for my $type (ref($types) eq 'ARRAY' ? @{ $types } : ( $types )) {
8a7f2a8a 152 next unless $COERCE{$type};
8a7f2a8a 153 for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
b3b74cc6 154 $_ = $value;
cceb0e25 155 next unless $TYPE{$coerce_type}->();
b3b74cc6 156 $_ = $value;
157 $_ = $COERCE{$type}->{$coerce_type}->();
158 return $_ if $type_constraint->();
4188b837 159 }
160 }
4188b837 161 return $value;
162}
163
d44f0d03 164sub enum {
165 my $name = shift;
166 my %is_valid = map { $_ => 1 } @_;
167
168 subtype(
169 $name => where => sub { $is_valid{$_} }
170 );
171}
172
d60c78b9 1731;
174
6feb83f1 175__END__
176
177=head1 NAME
178
3b46bd49 179Mouse::Util::TypeConstraints - simple type constraints
6feb83f1 180
181=head1 METHODS
182
183=head2 optimized_constraints -> HashRef[CODE]
184
185Returns the simple type constraints that Mouse understands.
186
187=cut
188
189