(failing) test for runtime roles and non-moose classes + does attrs
[gitmo/Moose.git] / lib / Moose / Meta / Class.pm
CommitLineData
c0e30cf5 1
2package Moose::Meta::Class;
3
4use strict;
5use warnings;
6
648e79ae 7use Class::MOP;
8
6ba6d68c 9use Carp 'confess';
54b1cdf0 10use Scalar::Util 'weaken', 'blessed', 'reftype';
a15dff8d 11
4498537c 12our $VERSION = '0.18';
d44714be 13our $AUTHORITY = 'cpan:STEVAN';
bc1e29b5 14
8ee73eeb 15use Moose::Meta::Method::Overriden;
16
c0e30cf5 17use base 'Class::MOP::Class';
18
598340d5 19__PACKAGE__->meta->add_attribute('roles' => (
ef333f17 20 reader => 'roles',
21 default => sub { [] }
22));
23
590868a3 24sub initialize {
25 my $class = shift;
26 my $pkg = shift;
27 $class->SUPER::initialize($pkg,
ac2dc464 28 'attribute_metaclass' => 'Moose::Meta::Attribute',
5cf3dbcf 29 'method_metaclass' => 'Moose::Meta::Method',
ac2dc464 30 'instance_metaclass' => 'Moose::Meta::Instance',
590868a3 31 @_);
ac2dc464 32}
590868a3 33
ef333f17 34sub add_role {
35 my ($self, $role) = @_;
36 (blessed($role) && $role->isa('Moose::Meta::Role'))
37 || confess "Roles must be instances of Moose::Meta::Role";
38 push @{$self->roles} => $role;
39}
40
b8aeb4dc 41sub calculate_all_roles {
42 my $self = shift;
43 my %seen;
44 grep { !$seen{$_->name}++ } map { $_->calculate_all_roles } @{ $self->roles };
45}
46
ef333f17 47sub does_role {
48 my ($self, $role_name) = @_;
49 (defined $role_name)
50 || confess "You must supply a role name to look for";
9c429218 51 foreach my $class ($self->class_precedence_list) {
ac2dc464 52 next unless $class->can('meta');
9c429218 53 foreach my $role (@{$class->meta->roles}) {
54 return 1 if $role->does_role($role_name);
55 }
ef333f17 56 }
57 return 0;
58}
59
d79e62fd 60sub excludes_role {
61 my ($self, $role_name) = @_;
62 (defined $role_name)
63 || confess "You must supply a role name to look for";
ac2dc464 64 foreach my $class ($self->class_precedence_list) {
65 next unless $class->can('meta');
5cb193ed 66 # NOTE:
67 # in the pretty rare instance when a Moose metaclass
ac2dc464 68 # is itself extended with a role, this check needs to
5cb193ed 69 # be done since some items in the class_precedence_list
ac2dc464 70 # might in fact be Class::MOP based still.
71 next unless $class->meta->can('roles');
9c429218 72 foreach my $role (@{$class->meta->roles}) {
73 return 1 if $role->excludes_role($role_name);
74 }
d79e62fd 75 }
76 return 0;
77}
78
8c9d74e7 79sub new_object {
80 my ($class, %params) = @_;
81 my $self = $class->SUPER::new_object(%params);
82 foreach my $attr ($class->compute_all_applicable_attributes()) {
715ea0b7 83 # FIXME:
84 # this does not accept undefined
ac2dc464 85 # values, nor does it accept false
715ea0b7 86 # values to be passed into the init-arg
5faf11bb 87 next unless $params{$attr->init_arg} && $attr->can('has_trigger') && $attr->has_trigger;
88 $attr->trigger->($self, $params{$attr->init_arg}, $attr);
8c9d74e7 89 }
ac2dc464 90 return $self;
8c9d74e7 91}
92
a15dff8d 93sub construct_instance {
94 my ($class, %params) = @_;
ddd0ec20 95 my $meta_instance = $class->get_meta_instance;
575db57d 96 # FIXME:
97 # the code below is almost certainly incorrect
98 # but this is foreign inheritence, so we might
ac2dc464 99 # have to kludge it in the end.
ddd0ec20 100 my $instance = $params{'__INSTANCE__'} || $meta_instance->create_instance();
ac2dc464 101 foreach my $attr ($class->compute_all_applicable_attributes()) {
ddd0ec20 102 $attr->initialize_instance_slot($meta_instance, $instance, \%params)
a15dff8d 103 }
104 return $instance;
105}
106
093b12c2 107# FIXME:
108# This is ugly
ac2dc464 109sub get_method_map {
093b12c2 110 my $self = shift;
53dd42d8 111
112 if (defined $self->{'$!_package_cache_flag'} &&
66e08a8a 113 $self->{'$!_package_cache_flag'} == Class::MOP::check_package_cache_flag($self->meta->name)) {
53dd42d8 114 return $self->{'%!methods'};
115 }
116
ac2dc464 117 my $map = $self->{'%!methods'};
118
093b12c2 119 my $class_name = $self->name;
120 my $method_metaclass = $self->method_metaclass;
ac2dc464 121
093b12c2 122 foreach my $symbol ($self->list_all_package_symbols('CODE')) {
ac2dc464 123
093b12c2 124 my $code = $self->get_package_symbol('&' . $symbol);
ac2dc464 125
126 next if exists $map->{$symbol} &&
127 defined $map->{$symbol} &&
128 $map->{$symbol}->body == $code;
129
53dd42d8 130 my ($pkg, $name) = Class::MOP::get_code_info($code);
ac2dc464 131
53dd42d8 132 if ($pkg->can('meta')
4f8f3aab 133 # NOTE:
134 # we don't know what ->meta we are calling
53dd42d8 135 # here, so we need to be careful cause it
136 # just might blow up at us, or just complain
137 # loudly (in the case of Curses.pm) so we
4f8f3aab 138 # just be a little overly cautious here.
139 # - SL
140 && eval { no warnings; blessed($pkg->meta) }
141 && $pkg->meta->isa('Moose::Meta::Role')) {
093b12c2 142 #my $role = $pkg->meta->name;
143 #next unless $self->does_role($role);
144 }
145 else {
53dd42d8 146 next if ($pkg || '') ne $class_name &&
147 ($name || '') ne '__ANON__';
148
093b12c2 149 }
ac2dc464 150
093b12c2 151 $map->{$symbol} = $method_metaclass->wrap($code);
152 }
ac2dc464 153
093b12c2 154 return $map;
a7d0cd00 155}
156
093b12c2 157### ---------------------------------------------
158
a2eec5e7 159sub add_attribute {
160 my $self = shift;
d9bb6c63 161 $self->SUPER::add_attribute($self->_process_attribute(@_));
a2eec5e7 162}
163
78cd1d3b 164sub add_override_method_modifier {
165 my ($self, $name, $method, $_super_package) = @_;
d05cd563 166 (!$self->has_method($name))
167 || confess "Cannot add an override method if a local method is already present";
78cd1d3b 168 # need this for roles ...
169 $_super_package ||= $self->name;
170 my $super = $self->find_next_method_by_name($name);
171 (defined $super)
ac2dc464 172 || confess "You cannot override '$name' because it has no super method";
093b12c2 173 $self->add_method($name => Moose::Meta::Method::Overriden->wrap(sub {
78cd1d3b 174 my @args = @_;
78cd1d3b 175 no warnings 'redefine';
52c7c330 176 if ($Moose::SUPER_SLOT{$_super_package}) {
b644e331 177 local *{$Moose::SUPER_SLOT{$_super_package}} = sub { $super->body->(@args) };
b468a3d3 178 return $method->(@args);
52c7c330 179 } else {
b468a3d3 180 confess "Trying to call override modifier'd method without super()";
52c7c330 181 }
093b12c2 182 }));
78cd1d3b 183}
184
185sub add_augment_method_modifier {
ac2dc464 186 my ($self, $name, $method) = @_;
d05cd563 187 (!$self->has_method($name))
ac2dc464 188 || confess "Cannot add an augment method if a local method is already present";
78cd1d3b 189 my $super = $self->find_next_method_by_name($name);
190 (defined $super)
ac2dc464 191 || confess "You cannot augment '$name' because it has no super method";
192 my $_super_package = $super->package_name;
193 # BUT!,... if this is an overriden method ....
05d9eaf6 194 if ($super->isa('Moose::Meta::Method::Overriden')) {
ac2dc464 195 # we need to be sure that we actually
196 # find the next method, which is not
05d9eaf6 197 # an 'override' method, the reason is
ac2dc464 198 # that an 'override' method will not
05d9eaf6 199 # be the one calling inner()
ac2dc464 200 my $real_super = $self->_find_next_method_by_name_which_is_not_overridden($name);
05d9eaf6 201 $_super_package = $real_super->package_name;
ac2dc464 202 }
78cd1d3b 203 $self->add_method($name => sub {
204 my @args = @_;
78cd1d3b 205 no warnings 'redefine';
52c7c330 206 if ($Moose::INNER_SLOT{$_super_package}) {
53dd42d8 207 local *{$Moose::INNER_SLOT{$_super_package}} = sub {
208 local *{$Moose::INNER_SLOT{$_super_package}} = sub {};
b468a3d3 209 $method->(@args);
210 };
b644e331 211 return $super->body->(@args);
53dd42d8 212 }
213 else {
b644e331 214 return $super->body->(@args);
52c7c330 215 }
ac2dc464 216 });
78cd1d3b 217}
218
1341f10c 219## Private Utility methods ...
220
05d9eaf6 221sub _find_next_method_by_name_which_is_not_overridden {
222 my ($self, $name) = @_;
68efb014 223 foreach my $method ($self->find_all_methods_by_name($name)) {
ac2dc464 224 return $method->{code}
05d9eaf6 225 if blessed($method->{code}) && !$method->{code}->isa('Moose::Meta::Method::Overriden');
226 }
227 return undef;
228}
229
1341f10c 230sub _fix_metaclass_incompatability {
231 my ($self, @superclasses) = @_;
232 foreach my $super (@superclasses) {
233 # don't bother if it does not have a meta.
234 next unless $super->can('meta');
ac2dc464 235 # get the name, make sure we take
8ecb1fa0 236 # immutable classes into account
ac2dc464 237 my $super_meta_name = ($super->meta->is_immutable
8ecb1fa0 238 ? $super->meta->get_mutable_metaclass_name
239 : blessed($super->meta));
ac2dc464 240 # if it's meta is a vanilla Moose,
241 # then we can safely ignore it.
8ecb1fa0 242 next if $super_meta_name eq 'Moose::Meta::Class';
ac2dc464 243 # but if we have anything else,
1341f10c 244 # we need to check it out ...
245 unless (# see if of our metaclass is incompatible
8ecb1fa0 246 ($self->isa($super_meta_name) &&
1341f10c 247 # and see if our instance metaclass is incompatible
248 $self->instance_metaclass->isa($super->meta->instance_metaclass)) &&
249 # ... and if we are just a vanilla Moose
250 $self->isa('Moose::Meta::Class')) {
251 # re-initialize the meta ...
252 my $super_meta = $super->meta;
253 # NOTE:
ac2dc464 254 # We might want to consider actually
255 # transfering any attributes from the
256 # original meta into this one, but in
1341f10c 257 # general you should not have any there
ac2dc464 258 # at this point anyway, so it's very
1341f10c 259 # much an obscure edge case anyway
260 $self = $super_meta->reinitialize($self->name => (
ac2dc464 261 'attribute_metaclass' => $super_meta->attribute_metaclass,
5cf3dbcf 262 'method_metaclass' => $super_meta->method_metaclass,
263 'instance_metaclass' => $super_meta->instance_metaclass,
1341f10c 264 ));
265 }
266 }
ac2dc464 267 return $self;
1341f10c 268}
269
d7d8a8c7 270# NOTE:
d9bb6c63 271# this was crap anyway, see
272# Moose::Util::apply_all_roles
d7d8a8c7 273# instead
4498537c 274sub _apply_all_roles {
275 die 'DEPRECATED: use Moose::Util::apply_all_roles($meta, @roles) instead'
276}
1341f10c 277
d9bb6c63 278my %ANON_CLASSES;
279
1341f10c 280sub _process_attribute {
d9bb6c63 281 my $self = shift;
282 my $name = shift;
283 my %options = ((scalar @_ == 1 && ref($_[0]) eq 'HASH') ? %{$_[0]} : @_);
284
1341f10c 285 if ($name =~ /^\+(.*)/) {
d7d8a8c7 286 return $self->_process_inherited_attribute($1, %options);
1341f10c 287 }
288 else {
d9bb6c63 289 my $attr_metaclass_name;
1341f10c 290 if ($options{metaclass}) {
c1935ade 291 my $metaclass_name = $options{metaclass};
292 eval {
293 my $possible_full_name = 'Moose::Meta::Attribute::Custom::' . $metaclass_name;
ac2dc464 294 Class::MOP::load_class($possible_full_name);
295 $metaclass_name = $possible_full_name->can('register_implementation')
c1935ade 296 ? $possible_full_name->register_implementation
297 : $possible_full_name;
298 };
299 if ($@) {
300 Class::MOP::load_class($metaclass_name);
301 }
d9bb6c63 302 $attr_metaclass_name = $metaclass_name;
1341f10c 303 }
304 else {
d9bb6c63 305 $attr_metaclass_name = $self->attribute_metaclass;
1341f10c 306 }
d9bb6c63 307
308 if ($options{traits}) {
309
310 my $anon_role_key = join "|" => @{$options{traits}};
311
312 my $class;
313 if (exists $ANON_CLASSES{$anon_role_key} && defined $ANON_CLASSES{$anon_role_key}) {
314 $class = $ANON_CLASSES{$anon_role_key};
315 }
316 else {
317 $class = Moose::Meta::Class->create_anon_class(
318 superclasses => [ $attr_metaclass_name ]
319 );
320 $ANON_CLASSES{$anon_role_key} = $class;
3bb22459 321
322 my @traits;
323 foreach my $trait (@{$options{traits}}) {
324 eval {
325 my $possible_full_name = 'Moose::Meta::Attribute::Custom::Trait::' . $trait;
326 Class::MOP::load_class($possible_full_name);
327 push @traits => $possible_full_name->can('register_implementation')
328 ? $possible_full_name->register_implementation
329 : $possible_full_name;
330 };
331 if ($@) {
332 push @traits => $trait;
333 }
334 }
335
336 Moose::Util::apply_all_roles($class, @traits);
d9bb6c63 337 }
338
339 $attr_metaclass_name = $class->name;
340 }
341
342 return $attr_metaclass_name->new($name, %options);
ac2dc464 343 }
1341f10c 344}
345
346sub _process_inherited_attribute {
347 my ($self, $attr_name, %options) = @_;
348 my $inherited_attr = $self->find_attribute_by_name($attr_name);
349 (defined $inherited_attr)
350 || confess "Could not find an attribute by the name of '$attr_name' to inherit from";
1341f10c 351 if ($inherited_attr->isa('Moose::Meta::Attribute')) {
d7d8a8c7 352 return $inherited_attr->clone_and_inherit_options(%options);
1341f10c 353 }
354 else {
355 # NOTE:
356 # kind of a kludge to handle Class::MOP::Attributes
d7d8a8c7 357 return $inherited_attr->Moose::Meta::Attribute::clone_and_inherit_options(%options);
ac2dc464 358 }
1341f10c 359}
360
5cf3dbcf 361## -------------------------------------------------
362
363use Moose::Meta::Method::Constructor;
1f779926 364use Moose::Meta::Method::Destructor;
5cf3dbcf 365
ac2dc464 366# This could be done by using SUPER and altering ->options
367# I am keeping it this way to make it more explicit.
368sub create_immutable_transformer {
369 my $self = shift;
370 my $class = Class::MOP::Immutable->new($self, {
371 read_only => [qw/superclasses/],
372 cannot_call => [qw/
373 add_method
374 alias_method
375 remove_method
376 add_attribute
377 remove_attribute
378 add_package_symbol
379 remove_package_symbol
380 add_role
381 /],
382 memoize => {
383 class_precedence_list => 'ARRAY',
384 compute_all_applicable_attributes => 'ARRAY',
385 get_meta_instance => 'SCALAR',
386 get_method_map => 'SCALAR',
387 # maybe ....
388 calculate_all_roles => 'ARRAY',
389 }
390 });
391 return $class;
392}
393
394sub make_immutable {
395 my $self = shift;
396 $self->SUPER::make_immutable
397 (
398 constructor_class => 'Moose::Meta::Method::Constructor',
399 destructor_class => 'Moose::Meta::Method::Destructor',
400 inline_destructor => 1,
401 # NOTE:
402 # no need to do this,
403 # Moose always does it
404 inline_accessors => 0,
405 @_,
406 );
5cf3dbcf 407}
408
c0e30cf5 4091;
410
411__END__
412
413=pod
414
415=head1 NAME
416
e522431d 417Moose::Meta::Class - The Moose metaclass
c0e30cf5 418
c0e30cf5 419=head1 DESCRIPTION
420
ac2dc464 421This is a subclass of L<Class::MOP::Class> with Moose specific
e522431d 422extensions.
423
ac2dc464 424For the most part, the only time you will ever encounter an
425instance of this class is if you are doing some serious deep
426introspection. To really understand this class, you need to refer
6ba6d68c 427to the L<Class::MOP::Class> documentation.
428
c0e30cf5 429=head1 METHODS
430
431=over 4
432
590868a3 433=item B<initialize>
434
5cf3dbcf 435=item B<make_immutable>
436
ac2dc464 437Override original to add default options for inlining destructor
438and altering the Constructor metaclass.
439
440=item B<create_immutable_transformer>
441
442Override original to lock C<add_role> and memoize C<calculate_all_roles>
443
8c9d74e7 444=item B<new_object>
445
02a0fb52 446We override this method to support the C<trigger> attribute option.
447
a15dff8d 448=item B<construct_instance>
449
ac2dc464 450This provides some Moose specific extensions to this method, you
451almost never call this method directly unless you really know what
452you are doing.
6ba6d68c 453
454This method makes sure to handle the moose weak-ref, type-constraint
ac2dc464 455and type coercion features.
ef1d5f4b 456
093b12c2 457=item B<get_method_map>
e9ec68d6 458
ac2dc464 459This accommodates Moose::Meta::Role::Method instances, which are
460aliased, instead of added, but still need to be counted as valid
e9ec68d6 461methods.
462
78cd1d3b 463=item B<add_override_method_modifier ($name, $method)>
464
ac2dc464 465This will create an C<override> method modifier for you, and install
02a0fb52 466it in the package.
467
78cd1d3b 468=item B<add_augment_method_modifier ($name, $method)>
469
ac2dc464 470This will create an C<augment> method modifier for you, and install
02a0fb52 471it in the package.
472
2b14ac61 473=item B<calculate_all_roles>
474
ef333f17 475=item B<roles>
476
ac2dc464 477This will return an array of C<Moose::Meta::Role> instances which are
02a0fb52 478attached to this class.
479
ef333f17 480=item B<add_role ($role)>
481
ac2dc464 482This takes an instance of C<Moose::Meta::Role> in C<$role>, and adds it
02a0fb52 483to the list of associated roles.
484
ef333f17 485=item B<does_role ($role_name)>
486
ac2dc464 487This will test if this class C<does> a given C<$role_name>. It will
488not only check it's local roles, but ask them as well in order to
02a0fb52 489cascade down the role hierarchy.
490
d79e62fd 491=item B<excludes_role ($role_name)>
492
ac2dc464 493This will test if this class C<excludes> a given C<$role_name>. It will
494not only check it's local roles, but ask them as well in order to
d79e62fd 495cascade down the role hierarchy.
496
9e93dd19 497=item B<add_attribute ($attr_name, %params|$params)>
4e848edb 498
9e93dd19 499This method does the same thing as L<Class::MOP::Class::add_attribute>, but adds
500support for taking the C<$params> as a HASH ref.
ac1ef2f9 501
c0e30cf5 502=back
503
504=head1 BUGS
505
ac2dc464 506All complex software has bugs lurking in it, and this module is no
c0e30cf5 507exception. If you find a bug please either email me, or add the bug
508to cpan-RT.
509
c0e30cf5 510=head1 AUTHOR
511
512Stevan Little E<lt>stevan@iinteractive.comE<gt>
513
514=head1 COPYRIGHT AND LICENSE
515
778db3ac 516Copyright 2006-2008 by Infinity Interactive, Inc.
c0e30cf5 517
518L<http://www.iinteractive.com>
519
520This library is free software; you can redistribute it and/or modify
ac2dc464 521it under the same terms as Perl itself.
c0e30cf5 522
8a7a9c53 523=cut
1a563243 524