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