move stuff from Meta::Attribute to Util::TypeConstraints, work with $_ and such
[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
321e5271 33BEGIN {
381f326a 34 no warnings 'uninitialized';
cceb0e25 35 %TYPE = (
381f326a 36 Any => sub { 1 },
37 Item => sub { 1 },
38 Bool => sub {
c91d12e0 39 !defined($_[0]) || $_[0] eq "" || "$_[0]" eq '1' || "$_[0]" eq '0'
381f326a 40 },
c91d12e0 41 Undef => sub { !defined($_[0]) },
42 Defined => sub { defined($_[0]) },
43 Value => sub { defined($_[0]) && !ref($_[0]) },
44 Num => sub { !ref($_[0]) && looks_like_number($_[0]) },
45 Int => sub { defined($_[0]) && !ref($_[0]) && $_[0] =~ /^-?[0-9]+$/ },
46 Str => sub { defined($_[0]) && !ref($_[0]) },
47 ClassName => sub { Mouse::is_class_loaded($_[0]) },
48 Ref => sub { ref($_[0]) },
49
50 ScalarRef => sub { ref($_[0]) eq 'SCALAR' },
51 ArrayRef => sub { ref($_[0]) eq 'ARRAY' },
52 HashRef => sub { ref($_[0]) eq 'HASH' },
53 CodeRef => sub { ref($_[0]) eq 'CODE' },
54 RegexpRef => sub { ref($_[0]) eq 'Regexp' },
55 GlobRef => sub { ref($_[0]) eq 'GLOB' },
381f326a 56
57 FileHandle => sub {
c91d12e0 58 ref($_[0]) eq 'GLOB' && openhandle($_[0])
381f326a 59 or
c91d12e0 60 blessed($_[0]) && $_[0]->isa("IO::Handle")
abe4e887 61 },
381f326a 62
c91d12e0 63 Object => sub { blessed($_[0]) && blessed($_[0]) ne 'Regexp' },
8a7f2a8a 64 );
d3982c7e 65
cceb0e25 66 sub optimized_constraints { \%TYPE }
67 my @TYPE_KEYS = keys %TYPE;
68 sub list_all_builtin_type_constraints { @TYPE_KEYS }
7dbebb1b 69
70 @TYPE_SOURCE{@TYPE_KEYS} = (__PACKAGE__) x @TYPE_KEYS;
381f326a 71}
d3982c7e 72
139d92d2 73sub type {
0d9fea22 74 my $pkg = caller(0);
75 my($name, %conf) = @_;
0d062abb 76 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
7dbebb1b 77 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
0d9fea22 78 };
321e5271 79 my $constraint = $conf{where} || do {
80 my $as = delete $conf{as} || 'Any';
81 if (! exists $TYPE{$as}) {
82 $TYPE{$as} = _build_type_constraint($as);
83 }
84 $TYPE{$as};
85 };
7dbebb1b 86
87 $TYPE_SOURCE{$name} = $pkg;
321e5271 88 $TYPE{$name} = sub { local $_ = $_[0]; $constraint->($_[0]) };
0d9fea22 89}
90
139d92d2 91sub subtype {
4188b837 92 my $pkg = caller(0);
61a02a3a 93 my($name, %conf) = @_;
0d062abb 94 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
7dbebb1b 95 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
61a02a3a 96 };
321e5271 97 my $constraint = $conf{where};
98 my $as_constraint = find_or_create_isa_type_constraint($conf{as} || 'Any');
7dbebb1b 99
100 $TYPE_SOURCE{$name} = $pkg;
321e5271 101 $TYPE{$name} = $constraint ?
102 sub {
103 local $_ = $_[0];
104 $as_constraint->($_[0]) && $constraint->($_[0])
105 } :
106 sub {
107 local $_ = $_[0];
108 $as_constraint->($_[0]);
109 }
110 ;
7dbebb1b 111
d9f8c878 112 return $name;
4188b837 113}
114
139d92d2 115sub coerce {
61a02a3a 116 my($name, %conf) = @_;
117
118 Carp::croak "Cannot find type '$name', perhaps you forgot to load it."
cceb0e25 119 unless $TYPE{$name};
61a02a3a 120
8a7f2a8a 121 unless ($COERCE{$name}) {
122 $COERCE{$name} = {};
123 $COERCE_KEYS{$name} = [];
124 }
61a02a3a 125 while (my($type, $code) = each %conf) {
126 Carp::croak "A coercion action already exists for '$type'"
8a7f2a8a 127 if $COERCE{$name}->{$type};
61a02a3a 128
310ad28b 129 if (! $TYPE{$type}) {
130 # looks parameterized
131 if ($type =~ /^[^\[]+\[.+\]$/) {
321e5271 132 _build_type_constraint($type);
310ad28b 133 } else {
134 Carp::croak "Could not find the type constraint ($type) to coerce from"
135 }
136 }
61a02a3a 137
8a7f2a8a 138 push @{ $COERCE_KEYS{$name} }, $type;
139 $COERCE{$name}->{$type} = $code;
61a02a3a 140 }
4188b837 141}
142
139d92d2 143sub class_type {
ecc6e3b1 144 my($name, $conf) = @_;
d9f8c878 145 if ($conf && $conf->{class}) {
146 # No, you're using this wrong
147 warn "class_type() should be class_type(ClassName). Perhaps you're looking for subtype $name => as '$conf->{class}'?";
148 subtype($name, as => $conf->{class});
149 } else {
150 subtype(
151 $name => where => sub { $_->isa($name) }
152 );
153 }
ecc6e3b1 154}
155
139d92d2 156sub role_type {
47f36c05 157 my($name, $conf) = @_;
158 my $role = $conf->{role};
139d92d2 159 subtype(
61a02a3a 160 $name => where => sub {
161 return unless defined $_ && ref($_) && $_->isa('Mouse::Object');
162 $_->meta->does_role($role);
163 }
164 );
47f36c05 165}
166
4188b837 167sub typecast_constraints {
eec1bb49 168 my($class, $pkg, $type_constraint, $types, $value) = @_;
eec1bb49 169
b3b74cc6 170 local $_;
eec1bb49 171 for my $type (ref($types) eq 'ARRAY' ? @{ $types } : ( $types )) {
8a7f2a8a 172 next unless $COERCE{$type};
8a7f2a8a 173 for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
b3b74cc6 174 $_ = $value;
c91d12e0 175 next unless $TYPE{$coerce_type}->($value);
b3b74cc6 176 $_ = $value;
c91d12e0 177 $_ = $COERCE{$type}->{$coerce_type}->($value);
178 return $_ if $type_constraint->($_);
4188b837 179 }
180 }
4188b837 181 return $value;
182}
183
01904723 184my $serial_enum = 0;
d44f0d03 185sub enum {
01904723 186 # enum ['small', 'medium', 'large']
187 if (ref($_[0]) eq 'ARRAY') {
188 my @elements = @{ shift @_ };
189
190 my $name = 'Mouse::Util::TypeConstaints::Enum::Serial::'
191 . ++$serial_enum;
192 enum($name, @elements);
193 return $name;
194 }
195
196 # enum size => 'small', 'medium', 'large'
d44f0d03 197 my $name = shift;
198 my %is_valid = map { $_ => 1 } @_;
199
200 subtype(
201 $name => where => sub { $is_valid{$_} }
202 );
203}
204
321e5271 205sub _build_type_constraint {
206
207 my $spec = shift;
208 my $code;
209 if ($spec =~ /^([^\[]+)\[(.+)\]$/) {
210 # parameterized
211 my $constraint = $1;
212 my $param = $2;
213 my $parent;
214 if ($constraint eq 'Maybe') {
215 $parent = _build_type_constraint('Undef');
216 } else {
217 $parent = _build_type_constraint($constraint);
218 }
219 my $child = _build_type_constraint($param);
220 if ($constraint eq 'ArrayRef') {
221 my $code_str =
222 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
223 "sub {\n" .
224 " if (\$parent->(\$_[0])) {\n" .
225 " foreach my \$e (\@{\$_[0]}) {\n" .
226 " return () unless \$child->(\$e);\n" .
227 " }\n" .
228 " return 1;\n" .
229 " }\n" .
230 " return ();\n" .
231 "};\n"
232 ;
233 $code = eval $code_str or Carp::confess("Failed to generate inline type constraint: $@");
234 } elsif ($constraint eq 'HashRef') {
235 my $code_str =
236 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
237 "sub {\n" .
238 " if (\$parent->(\$_[0])) {\n" .
239 " foreach my \$e (values \%{\$_[0]}) {\n" .
240 " return () unless \$child->(\$e);\n" .
241 " }\n" .
242 " return 1;\n" .
243 " }\n" .
244 " return ();\n" .
245 "};\n"
246 ;
247 $code = eval $code_str or Carp::confess($@);
248 } elsif ($constraint eq 'Maybe') {
249 my $code_str =
250 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
251 "sub {\n" .
252 " return \$child->(\$_[0]) || \$parent->(\$_[0]);\n" .
253 "};\n"
254 ;
255 $code = eval $code_str or Carp::confess($@);
256 } else {
257 Carp::confess("Support for parameterized types other than ArrayRef or HashRef is not implemented yet");
258 }
259 $TYPE{$spec} = $code;
260 } else {
261 $code = $TYPE{ $spec };
262 if (! $code) {
263 my $code_str =
264 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
265 "sub {\n" .
266 " Scalar::Util::blessed(\$_[0]) && \$_[0]->isa('$spec');\n" .
267 "}"
268 ;
269 $code = eval $code_str or Carp::confess($@);
270 $TYPE{$spec} = $code;
271 }
272 }
273 return $code;
274}
275
276sub find_type_constraint {
277 my $type_constraint = shift;
278 return $TYPE{$type_constraint};
279}
280
281sub find_or_create_isa_type_constraint {
282 my $type_constraint = shift;
283
284 my $code;
285
286 $type_constraint =~ s/\s+//g;
287 my @type_constraints = split /\|/, $type_constraint;
288 if (@type_constraints == 1) {
289 $code = $TYPE{$type_constraints[0]} ||
290 _build_type_constraint($type_constraints[0]);
291 } else {
292 my @code_list = map {
293 $TYPE{$_} || _build_type_constraint($_)
294 } @type_constraints;
295 $code = sub {
296 my $i = 0;
297 for my $code (@code_list) {
298 return 1 if $code->($_[0]);
299 }
300 return 0;
301 };
302 }
303 return $code;
304}
305
d60c78b9 3061;
307
6feb83f1 308__END__
309
310=head1 NAME
311
3b46bd49 312Mouse::Util::TypeConstraints - simple type constraints
6feb83f1 313
314=head1 METHODS
315
316=head2 optimized_constraints -> HashRef[CODE]
317
318Returns the simple type constraints that Mouse understands.
319
c91d12e0 320=head1 FUNCTIONS
321
322=over 4
323
324=item B<subtype 'Name' => as 'Parent' => where { } ...>
325
326=item B<subtype as 'Parent' => where { } ...>
327
328=item B<class_type ($class, ?$options)>
329
330=item B<role_type ($role, ?$options)>
331
332=item B<enum (\@values)>
333
334=back
335
6feb83f1 336=cut
337
338