fix portability between Moose/Mouse's class_type
[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 {
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 );
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;
c91d12e0 84 $TYPE{$name} = sub { local $_=$_[0]; $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 };
310ad28b 93 my $constraint = $conf{where} || do {
94 my $as = delete $conf{as} || 'Any';
95 if (! exists $TYPE{$as}) { # Perhaps it's a parameterized source?
96 Mouse::Meta::Attribute::_build_type_constraint($as);
97 }
98 $TYPE{$as};
99 };
7dbebb1b 100 my $as = $conf{as} || '';
101
102 $TYPE_SOURCE{$name} = $pkg;
103
cceb0e25 104 if ($as = $TYPE{$as}) {
c91d12e0 105 $TYPE{$name} = sub { local $_=$_[0]; $as->($_) && $constraint->($_) };
1fbefea5 106 } else {
c91d12e0 107 $TYPE{$name} = sub { local $_=$_[0]; $constraint->($_) };
1fbefea5 108 }
d9f8c878 109 return $name;
4188b837 110}
111
139d92d2 112sub coerce {
61a02a3a 113 my($name, %conf) = @_;
114
115 Carp::croak "Cannot find type '$name', perhaps you forgot to load it."
cceb0e25 116 unless $TYPE{$name};
61a02a3a 117
8a7f2a8a 118 unless ($COERCE{$name}) {
119 $COERCE{$name} = {};
120 $COERCE_KEYS{$name} = [];
121 }
61a02a3a 122 while (my($type, $code) = each %conf) {
123 Carp::croak "A coercion action already exists for '$type'"
8a7f2a8a 124 if $COERCE{$name}->{$type};
61a02a3a 125
310ad28b 126 if (! $TYPE{$type}) {
127 # looks parameterized
128 if ($type =~ /^[^\[]+\[.+\]$/) {
129 Mouse::Meta::Attribute::_build_type_constraint($type);
130 } else {
131 Carp::croak "Could not find the type constraint ($type) to coerce from"
132 }
133 }
61a02a3a 134
8a7f2a8a 135 push @{ $COERCE_KEYS{$name} }, $type;
136 $COERCE{$name}->{$type} = $code;
61a02a3a 137 }
4188b837 138}
139
139d92d2 140sub class_type {
ecc6e3b1 141 my($name, $conf) = @_;
d9f8c878 142 if ($conf && $conf->{class}) {
143 # No, you're using this wrong
144 warn "class_type() should be class_type(ClassName). Perhaps you're looking for subtype $name => as '$conf->{class}'?";
145 subtype($name, as => $conf->{class});
146 } else {
147 subtype(
148 $name => where => sub { $_->isa($name) }
149 );
150 }
ecc6e3b1 151}
152
139d92d2 153sub role_type {
47f36c05 154 my($name, $conf) = @_;
155 my $role = $conf->{role};
139d92d2 156 subtype(
61a02a3a 157 $name => where => sub {
158 return unless defined $_ && ref($_) && $_->isa('Mouse::Object');
159 $_->meta->does_role($role);
160 }
161 );
47f36c05 162}
163
4188b837 164sub typecast_constraints {
eec1bb49 165 my($class, $pkg, $type_constraint, $types, $value) = @_;
eec1bb49 166
b3b74cc6 167 local $_;
eec1bb49 168 for my $type (ref($types) eq 'ARRAY' ? @{ $types } : ( $types )) {
8a7f2a8a 169 next unless $COERCE{$type};
8a7f2a8a 170 for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
b3b74cc6 171 $_ = $value;
c91d12e0 172 next unless $TYPE{$coerce_type}->($value);
b3b74cc6 173 $_ = $value;
c91d12e0 174 $_ = $COERCE{$type}->{$coerce_type}->($value);
175 return $_ if $type_constraint->($_);
4188b837 176 }
177 }
4188b837 178 return $value;
179}
180
01904723 181my $serial_enum = 0;
d44f0d03 182sub enum {
01904723 183 # enum ['small', 'medium', 'large']
184 if (ref($_[0]) eq 'ARRAY') {
185 my @elements = @{ shift @_ };
186
187 my $name = 'Mouse::Util::TypeConstaints::Enum::Serial::'
188 . ++$serial_enum;
189 enum($name, @elements);
190 return $name;
191 }
192
193 # enum size => 'small', 'medium', 'large'
d44f0d03 194 my $name = shift;
195 my %is_valid = map { $_ => 1 } @_;
196
197 subtype(
198 $name => where => sub { $is_valid{$_} }
199 );
200}
201
d60c78b9 2021;
203
6feb83f1 204__END__
205
206=head1 NAME
207
3b46bd49 208Mouse::Util::TypeConstraints - simple type constraints
6feb83f1 209
210=head1 METHODS
211
212=head2 optimized_constraints -> HashRef[CODE]
213
214Returns the simple type constraints that Mouse understands.
215
c91d12e0 216=head1 FUNCTIONS
217
218=over 4
219
220=item B<subtype 'Name' => as 'Parent' => where { } ...>
221
222=item B<subtype as 'Parent' => where { } ...>
223
224=item B<class_type ($class, ?$options)>
225
226=item B<role_type ($role, ?$options)>
227
228=item B<enum (\@values)>
229
230=back
231
6feb83f1 232=cut
233
234