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