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