class_type shouldn't load the class (Moose compat; no easy fix :/)
[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     _subtype(
147         $name => where => sub { $_->isa($class) }
148     );
149 }
150
151 sub _role_type {
152     my($name, $conf) = @_;
153     my $role = $conf->{role};
154     _subtype(
155         $name => where => sub {
156             return unless defined $_ && ref($_) && $_->isa('Mouse::Object');
157             $_->meta->does_role($role);
158         }
159     );
160 }
161
162 sub typecast_constraints {
163     my($class, $pkg, $type_constraint, $types, $value) = @_;
164
165     local $_;
166     for my $type (ref($types) eq 'ARRAY' ? @{ $types } : ( $types )) {
167         next unless $COERCE{$type};
168         for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
169             $_ = $value;
170             next unless $TYPE{$coerce_type}->();
171             $_ = $value;
172             $_ = $COERCE{$type}->{$coerce_type}->();
173             return $_ if $type_constraint->();
174         }
175     }
176     return $value;
177 }
178
179 1;
180
181 __END__
182
183 =head1 NAME
184
185 Mouse::Util::TypeConstraints - simple type constraints
186
187 =head1 METHODS
188
189 =head2 optimized_constraints -> HashRef[CODE]
190
191 Returns the simple type constraints that Mouse understands.
192
193 =cut
194
195