You can redefine types in the original package
[gitmo/Mouse.git] / lib / Mouse / Util / TypeConstraints.pm
1 package Mouse::Util::TypeConstraints;
2 use strict;
3 use warnings;
4
5 use Carp ();
6 use Scalar::Util qw/blessed looks_like_number openhandle/;
7
8 my %TYPE;
9 my %TYPE_SOURCE;
10 my %COERCE;
11 my %COERCE_KEYS;
12
13 #find_type_constraint register_type_constraint
14 sub import {
15     my $class  = shift;
16     my %args   = @_;
17     my $caller = $args{callee} || caller(0);
18
19     no strict 'refs';
20     *{"$caller\::as"}          = \&_as;
21     *{"$caller\::where"}       = \&_where;
22     *{"$caller\::message"}     = \&_message;
23     *{"$caller\::from"}        = \&_from;
24     *{"$caller\::via"}         = \&_via;
25     *{"$caller\::type"}        = \&_type;
26     *{"$caller\::subtype"}     = \&_subtype;
27     *{"$caller\::coerce"}      = \&_coerce;
28     *{"$caller\::class_type"}  = \&_class_type;
29     *{"$caller\::role_type"}   = \&_role_type;
30 }
31
32
33 sub _as ($) {
34     as => $_[0]
35 }
36 sub _where (&) {
37     where => $_[0]
38 }
39 sub _message ($) {
40     message => $_[0]
41 }
42
43 sub _from { @_ }
44 sub _via (&) {
45     $_[0]
46 }
47
48 my $optimized_constraints;
49 my $optimized_constraints_base;
50 {
51     no warnings 'uninitialized';
52     %TYPE = (
53         Any        => sub { 1 },
54         Item       => sub { 1 },
55         Bool       => sub {
56             !defined($_) || $_ eq "" || "$_" eq '1' || "$_" eq '0'
57         },
58         Undef      => sub { !defined($_) },
59         Defined    => sub { defined($_) },
60         Value      => sub { defined($_) && !ref($_) },
61         Num        => sub { !ref($_) && looks_like_number($_) },
62         Int        => sub { defined($_) && !ref($_) && /^-?[0-9]+$/ },
63         Str        => sub { defined($_) && !ref($_) },
64         ClassName  => sub { Mouse::is_class_loaded($_) },
65         Ref        => sub { ref($_) },
66
67         ScalarRef  => sub { ref($_) eq 'SCALAR' },
68         ArrayRef   => sub { ref($_) eq 'ARRAY'  },
69         HashRef    => sub { ref($_) eq 'HASH'   },
70         CodeRef    => sub { ref($_) eq 'CODE'   },
71         RegexpRef  => sub { ref($_) eq 'Regexp' },
72         GlobRef    => sub { ref($_) eq 'GLOB'   },
73
74         FileHandle => sub {
75             ref($_) eq 'GLOB' && openhandle($_)
76             or
77             blessed($_) && $_->isa("IO::Handle")
78         },
79
80         Object     => sub { blessed($_) && blessed($_) ne 'Regexp' },
81     );
82
83     sub optimized_constraints { \%TYPE }
84     my @TYPE_KEYS = keys %TYPE;
85     sub list_all_builtin_type_constraints { @TYPE_KEYS }
86
87     @TYPE_SOURCE{@TYPE_KEYS} = (__PACKAGE__) x @TYPE_KEYS;
88 }
89
90 sub _type {
91     my $pkg = caller(0);
92     my($name, %conf) = @_;
93     if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
94         Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
95     };
96     my $constraint = $conf{where} || do { $TYPE{delete $conf{as} || 'Any' } };
97
98     $TYPE_SOURCE{$name} = $pkg;
99     $TYPE{$name} = $constraint;
100 }
101
102 sub _subtype {
103     my $pkg = caller(0);
104     my($name, %conf) = @_;
105     if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
106         Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
107     };
108     my $constraint = $conf{where} || do { $TYPE{delete $conf{as} || 'Any' } };
109     my $as         = $conf{as} || '';
110
111     $TYPE_SOURCE{$name} = $pkg;
112
113     if ($as = $TYPE{$as}) {
114         $TYPE{$name} = sub { $as->($_) && $constraint->($_) };
115     } else {
116         $TYPE{$name} = $constraint;
117     }
118 }
119
120 sub _coerce {
121     my($name, %conf) = @_;
122
123     Carp::croak "Cannot find type '$name', perhaps you forgot to load it."
124         unless $TYPE{$name};
125
126     unless ($COERCE{$name}) {
127         $COERCE{$name}      = {};
128         $COERCE_KEYS{$name} = [];
129     }
130     while (my($type, $code) = each %conf) {
131         Carp::croak "A coercion action already exists for '$type'"
132             if $COERCE{$name}->{$type};
133
134         Carp::croak "Could not find the type constraint ($type) to coerce from"
135             unless $TYPE{$type};
136
137         push @{ $COERCE_KEYS{$name} }, $type;
138         $COERCE{$name}->{$type} = $code;
139     }
140 }
141
142 sub _class_type {
143     my $pkg = caller(0);
144     my($name, $conf) = @_;
145     my $class = $conf->{class};
146     Mouse::load_class($class);
147     _subtype(
148         $name => where => sub { $_->isa($class) }
149     );
150 }
151
152 sub _role_type {
153     my($name, $conf) = @_;
154     my $role = $conf->{role};
155     _subtype(
156         $name => where => sub {
157             return unless defined $_ && ref($_) && $_->isa('Mouse::Object');
158             $_->meta->does_role($role);
159         }
160     );
161 }
162
163 sub typecast_constraints {
164     my($class, $pkg, $type_constraint, $types, $value) = @_;
165
166     local $_;
167     for my $type (ref($types) eq 'ARRAY' ? @{ $types } : ( $types )) {
168         next unless $COERCE{$type};
169         for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
170             $_ = $value;
171             next unless $TYPE{$coerce_type}->();
172             $_ = $value;
173             $_ = $COERCE{$type}->{$coerce_type}->();
174             return $_ if $type_constraint->();
175         }
176     }
177     return $value;
178 }
179
180 1;
181
182 __END__
183
184 =head1 NAME
185
186 Mouse::Util::TypeConstraints - simple type constraints
187
188 =head1 METHODS
189
190 =head2 optimized_constraints -> HashRef[CODE]
191
192 Returns the simple type constraints that Mouse understands.
193
194 =cut
195
196