X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FClass%2FMOP%2FClass.pm;h=d54fdf92b449f4297ca4a7e9f0e001612af65276;hb=bd2550f8320262fe1ab10f6c0eedc960889d869f;hp=325c913590d11e8f2273acb881f78d0326ccc878;hpb=b74f98bec65307feced9de906140546c4dbc31d8;p=gitmo%2FClass-MOP.git diff --git a/lib/Class/MOP/Class.pm b/lib/Class/MOP/Class.pm index 325c913..d54fdf9 100644 --- a/lib/Class/MOP/Class.pm +++ b/lib/Class/MOP/Class.pm @@ -8,6 +8,7 @@ use Class::MOP::Instance; use Class::MOP::Method::Wrapped; use Class::MOP::Method::Accessor; use Class::MOP::Method::Constructor; +use Class::MOP::MiniTrait; use Carp 'confess'; use Scalar::Util 'blessed', 'reftype', 'weaken'; @@ -16,7 +17,7 @@ use Devel::GlobalDestruction 'in_global_destruction'; use Try::Tiny; use List::MoreUtils 'all'; -our $VERSION = '1.01'; +our $VERSION = '1.12'; $VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; @@ -45,6 +46,25 @@ sub initialize { || $class->_construct_class_instance(package => $package_name, @_); } +sub reinitialize { + my ( $class, @args ) = @_; + unshift @args, "package" if @args % 2; + my %options = @args; + my $old_metaclass = blessed($options{package}) + ? $options{package} + : Class::MOP::get_metaclass_by_name($options{package}); + $options{weaken} = Class::MOP::metaclass_is_weak($old_metaclass->name) + if !exists $options{weaken} + && blessed($old_metaclass) + && $old_metaclass->isa('Class::MOP::Class'); + $old_metaclass->_remove_generated_metaobjects + if $old_metaclass && $old_metaclass->isa('Class::MOP::Class'); + my $new_metaclass = $class->SUPER::reinitialize(%options); + $new_metaclass->_restore_metaobjects_from($old_metaclass) + if $old_metaclass && $old_metaclass->isa('Class::MOP::Class'); + return $new_metaclass; +} + # NOTE: (meta-circularity) # this is a special form of _construct_instance # (see below), which is used to construct class @@ -93,7 +113,7 @@ sub _construct_class_instance { # NOTE: # we need to weaken any anon classes # so that they can call DESTROY properly - Class::MOP::weaken_metaclass($package_name) if $meta->is_anon_class; + Class::MOP::weaken_metaclass($package_name) if $options->{weaken}; $meta; } @@ -190,43 +210,33 @@ sub update_package_cache_flag { sub _check_metaclass_compatibility { my $self = shift; - if (my @superclasses = $self->superclasses) { - $self->_fix_metaclass_incompatibility(@superclasses); + my @superclasses = $self->superclasses + or return; - my %base_metaclass = $self->_base_metaclasses; + $self->_fix_metaclass_incompatibility(@superclasses); - # this is always okay ... - return if ref($self) eq 'Class::MOP::Class' + my %base_metaclass = $self->_base_metaclasses; + + # this is always okay ... + return + if ref($self) eq 'Class::MOP::Class' && all { my $meta = $self->$_; - !defined($meta) || $meta eq $base_metaclass{$_} - } keys %base_metaclass; - - for my $superclass (@superclasses) { - $self->_check_class_metaclass_compatibility($superclass); + !defined($meta) || $meta eq $base_metaclass{$_}; } + keys %base_metaclass; - for my $metaclass_type (keys %base_metaclass) { - next unless defined $self->$metaclass_type; - for my $superclass (@superclasses) { - $self->_check_single_metaclass_compatibility( - $metaclass_type, $superclass - ); - } - } + for my $superclass (@superclasses) { + $self->_check_class_metaclass_compatibility($superclass); } -} - -sub _class_metaclass_is_compatible { - my $self = shift; - my ( $superclass_name ) = @_; - my $super_meta = Class::MOP::get_metaclass_by_name($superclass_name) - || return 1; - - my $super_meta_type = $super_meta->_real_ref_name; - - return $self->isa($super_meta_type); + for my $metaclass_type ( keys %base_metaclass ) { + next unless defined $self->$metaclass_type; + for my $superclass (@superclasses) { + $self->_check_single_metaclass_compatibility( $metaclass_type, + $superclass ); + } + } } sub _check_class_metaclass_compatibility { @@ -245,22 +255,16 @@ sub _check_class_metaclass_compatibility { } } -sub _single_metaclass_is_compatible { +sub _class_metaclass_is_compatible { my $self = shift; - my ( $metaclass_type, $superclass_name ) = @_; + my ( $superclass_name ) = @_; my $super_meta = Class::MOP::get_metaclass_by_name($superclass_name) || return 1; - # for instance, Moose::Meta::Class has a error_class attribute, but - # Class::MOP::Class doesn't - this shouldn't be an error - return 1 unless $super_meta->can($metaclass_type); - # for instance, Moose::Meta::Class has a destructor_class, but - # Class::MOP::Class doesn't - this shouldn't be an error - return 1 if defined $self->$metaclass_type - && !defined $super_meta->$metaclass_type; + my $super_meta_name = $super_meta->_real_ref_name; - return $self->$metaclass_type->isa($super_meta->$metaclass_type); + return $self->_is_compatible_with($super_meta_name); } sub _check_single_metaclass_compatibility { @@ -276,70 +280,43 @@ sub _check_single_metaclass_compatibility { . $self->name . " (" . ($self->$metaclass_type) . ")" . " is not compatible with the " . "$metaclass_type_name metaclass of its " - . "superclass, " . $superclass_name . " (" + . "superclass, $superclass_name (" . ($super_meta->$metaclass_type) . ")"; } } -sub _can_fix_class_metaclass_incompatibility_by_subclassing { - my $self = shift; - my ($super_meta) = @_; - - my $super_meta_type = $super_meta->_real_ref_name; - - return $super_meta_type ne blessed($self) - && $super_meta->isa(blessed($self)); -} - -sub _can_fix_single_metaclass_incompatibility_by_subclassing { +sub _single_metaclass_is_compatible { my $self = shift; - my ($metaclass_type, $super_meta) = @_; + my ( $metaclass_type, $superclass_name ) = @_; - my $specific_meta = $self->$metaclass_type; - return unless $super_meta->can($metaclass_type); - my $super_specific_meta = $super_meta->$metaclass_type; + my $super_meta = Class::MOP::get_metaclass_by_name($superclass_name) + || return 1; + # for instance, Moose::Meta::Class has a error_class attribute, but + # Class::MOP::Class doesn't - this shouldn't be an error + return 1 unless $super_meta->can($metaclass_type); # for instance, Moose::Meta::Class has a destructor_class, but # Class::MOP::Class doesn't - this shouldn't be an error - return if defined $specific_meta - && !defined $super_specific_meta; + return 1 unless defined $super_meta->$metaclass_type; + # if metaclass is defined in superclass but not here, it's not compatible + # this is a really odd case + return 0 unless defined $self->$metaclass_type; - return $specific_meta ne $super_specific_meta - && $super_specific_meta->isa($specific_meta); -} - -sub _can_fix_metaclass_incompatibility_by_subclassing { - my $self = shift; - my ($super_meta) = @_; - - return 1 if $self->_can_fix_class_metaclass_incompatibility_by_subclassing($super_meta); - - my %base_metaclass = $self->_base_metaclasses; - for my $metaclass_type (keys %base_metaclass) { - next unless defined $self->$metaclass_type; - return 1 if $self->_can_fix_single_metaclass_incompatibility_by_subclassing($metaclass_type, $super_meta); - } - - return; -} - -sub _can_fix_metaclass_incompatibility { - my $self = shift; - return $self->_can_fix_metaclass_incompatibility_by_subclassing(@_); + return $self->$metaclass_type->_is_compatible_with($super_meta->$metaclass_type); } sub _fix_metaclass_incompatibility { my $self = shift; - my @supers = @_; + my @supers = map { Class::MOP::Class->initialize($_) } @_; my $necessary = 0; - for my $super (map { Class::MOP::Class->initialize($_) } @supers) { + for my $super (@supers) { $necessary = 1 if $self->_can_fix_metaclass_incompatibility($super); } return unless $necessary; - for my $super (map { Class::MOP::Class->initialize($_) } @supers) { + for my $super (@supers) { if (!$self->_class_metaclass_is_compatible($super->name)) { $self->_fix_class_metaclass_incompatibility($super); } @@ -347,8 +324,7 @@ sub _fix_metaclass_incompatibility { my %base_metaclass = $self->_base_metaclasses; for my $metaclass_type (keys %base_metaclass) { - next unless defined $self->$metaclass_type; - for my $super (map { Class::MOP::Class->initialize($_) } @supers) { + for my $super (@supers) { if (!$self->_single_metaclass_is_compatible($metaclass_type, $super->name)) { $self->_fix_single_metaclass_incompatibility( $metaclass_type, $super @@ -358,11 +334,52 @@ sub _fix_metaclass_incompatibility { } } +sub _can_fix_metaclass_incompatibility { + my $self = shift; + my ($super_meta) = @_; + + return 1 if $self->_class_metaclass_can_be_made_compatible($super_meta); + + my %base_metaclass = $self->_base_metaclasses; + for my $metaclass_type (keys %base_metaclass) { + return 1 if $self->_single_metaclass_can_be_made_compatible($super_meta, $metaclass_type); + } + + return; +} + +sub _class_metaclass_can_be_made_compatible { + my $self = shift; + my ($super_meta) = @_; + + return $self->_can_be_made_compatible_with($super_meta->_real_ref_name); +} + +sub _single_metaclass_can_be_made_compatible { + my $self = shift; + my ($super_meta, $metaclass_type) = @_; + + my $specific_meta = $self->$metaclass_type; + + return unless $super_meta->can($metaclass_type); + my $super_specific_meta = $super_meta->$metaclass_type; + + # for instance, Moose::Meta::Class has a destructor_class, but + # Class::MOP::Class doesn't - this shouldn't be an error + return unless defined $super_specific_meta; + + # if metaclass is defined in superclass but not here, it's fixable + # this is a really odd case + return 1 unless defined $specific_meta; + + return 1 if $specific_meta->_can_be_made_compatible_with($super_specific_meta); +} + sub _fix_class_metaclass_incompatibility { my $self = shift; my ( $super_meta ) = @_; - if ($self->_can_fix_class_metaclass_incompatibility_by_subclassing($super_meta)) { + if ($self->_class_metaclass_can_be_made_compatible($super_meta)) { ($self->is_pristine) || confess "Can't fix metaclass incompatibility for " . $self->name @@ -370,7 +387,7 @@ sub _fix_class_metaclass_incompatibility { my $super_meta_name = $super_meta->_real_ref_name; - $super_meta_name->meta->rebless_instance($self); + $self->_make_compatible_with($super_meta_name); } } @@ -378,13 +395,32 @@ sub _fix_single_metaclass_incompatibility { my $self = shift; my ( $metaclass_type, $super_meta ) = @_; - if ($self->_can_fix_single_metaclass_incompatibility_by_subclassing($metaclass_type, $super_meta)) { + if ($self->_single_metaclass_can_be_made_compatible($super_meta, $metaclass_type)) { ($self->is_pristine) || confess "Can't fix metaclass incompatibility for " . $self->name . " because it is not pristine."; - $self->{$metaclass_type} = $super_meta->$metaclass_type; + my $new_metaclass = $self->$metaclass_type + ? $self->$metaclass_type->_get_compatible_metaclass($super_meta->$metaclass_type) + : $super_meta->$metaclass_type; + $self->{$metaclass_type} = $new_metaclass; + } +} + +sub _restore_metaobjects_from { + my $self = shift; + my ($old_meta) = @_; + + $self->_restore_metamethods_from($old_meta); + $self->_restore_metaattributes_from($old_meta); +} + +sub _remove_generated_metaobjects { + my $self = shift; + + for my $attr (map { $self->get_attribute($_) } $self->get_attribute_list) { + $attr->remove_accessors; } } @@ -412,6 +448,7 @@ sub _fix_single_metaclass_incompatibility { sub create_anon_class { my ($class, %options) = @_; + $options{weaken} = 1 unless exists $options{weaken}; my $package_name = $ANON_CLASS_PREFIX . ++$ANON_CLASS_SERIAL; return $class->create($package_name, %options); } @@ -472,12 +509,16 @@ sub create { || confess "You must pass a HASH ref of methods" if exists $options{methods}; + $options{meta_name} = 'meta' + unless exists $options{meta_name}; + my (%initialize_options) = @args; delete @initialize_options{qw( package superclasses attributes methods + meta_name version authority )}; @@ -485,10 +526,8 @@ sub create { $meta->_instantiate_module( $options{version}, $options{authority} ); - # FIXME totally lame - $meta->add_method('meta' => sub { - $class->initialize(ref($_[0]) || $_[0]); - }); + $meta->_add_meta_method($options{meta_name}) + if defined $options{meta_name}; $meta->superclasses(@{$options{superclasses}}) if exists $options{superclasses}; @@ -563,17 +602,8 @@ sub _construct_instance { foreach my $attr ($class->get_all_attributes()) { $attr->initialize_instance_slot($meta_instance, $instance, $params); } - # NOTE: - # this will only work for a HASH instance type - if ($class->is_anon_class) { - (reftype($instance) eq 'HASH') - || confess "Currently only HASH based instances are supported with instance of anon-classes"; - # NOTE: - # At some point we should make this official - # as a reserved slot name, but right now I am - # going to keep it here. - # my $RESERVED_MOP_SLOT = '__MOP__'; - $instance->{'__MOP__'} = $class; + if (Class::MOP::metaclass_is_weak($class->name)) { + $meta_instance->_set_mop_slot($instance, $class); } return $instance; } @@ -598,6 +628,36 @@ sub _create_meta_instance { return $instance; } +sub inline_create_instance { + my $self = shift; + + return $self->get_meta_instance->inline_create_instance(@_); +} + +sub inline_rebless_instance { + my $self = shift; + + return $self->get_meta_instance->inline_rebless_instance_structure(@_); +} + +sub _inline_get_mop_slot { + my $self = shift; + + return $self->get_meta_instance->_inline_get_mop_slot(@_); +} + +sub _inline_set_mop_slot { + my $self = shift; + + return $self->get_meta_instance->_inline_set_mop_slot(@_); +} + +sub _inline_clear_mop_slot { + my $self = shift; + + return $self->get_meta_instance->_inline_clear_mop_slot(@_); +} + sub clone_object { my $class = shift; my $instance = shift; @@ -628,46 +688,45 @@ sub _clone_instance { return $clone; } -sub rebless_instance { +sub _force_rebless_instance { my ($self, $instance, %params) = @_; - my $old_metaclass = Class::MOP::class_of($instance); - my $old_class = $old_metaclass ? $old_metaclass->name : blessed($instance); - $self->name->isa($old_class) - || confess "You may rebless only into a subclass of ($old_class), of which (". $self->name .") isn't."; - $old_metaclass->rebless_instance_away($instance, $self, %params) if $old_metaclass; - my $meta_instance = $self->get_meta_instance(); + my $meta_instance = $self->get_meta_instance; + + if (Class::MOP::metaclass_is_weak($old_metaclass->name)) { + $meta_instance->_clear_mop_slot($instance); + } # rebless! # we use $_[1] here because of t/306_rebless_overload.t regressions on 5.8.8 $meta_instance->rebless_instance_structure($_[1], $self); - foreach my $attr ( $self->get_all_attributes ) { - if ( $attr->has_value($instance) ) { - if ( defined( my $init_arg = $attr->init_arg ) ) { - $params{$init_arg} = $attr->get_value($instance) - unless exists $params{$init_arg}; - } - else { - $attr->set_value($instance, $attr->get_value($instance)); - } - } - } + $self->_fixup_attributes_after_rebless($instance, $old_metaclass, %params); - foreach my $attr ($self->get_all_attributes) { - $attr->initialize_instance_slot($meta_instance, $instance, \%params); + if (Class::MOP::metaclass_is_weak($self->name)) { + $meta_instance->_set_mop_slot($instance, $self); } - - $instance; +} + +sub rebless_instance { + my ($self, $instance, %params) = @_; + my $old_metaclass = Class::MOP::class_of($instance); + + my $old_class = $old_metaclass ? $old_metaclass->name : blessed($instance); + $self->name->isa($old_class) + || confess "You may rebless only into a subclass of ($old_class), of which (". $self->name .") isn't."; + + $self->_force_rebless_instance($_[1], %params); + + return $instance; } sub rebless_instance_back { my ( $self, $instance ) = @_; - my $old_metaclass = Class::MOP::class_of($instance); my $old_class @@ -678,24 +737,40 @@ sub rebless_instance_back { . $self->name . ") isn't."; - $old_metaclass->rebless_instance_away( $instance, $self ) - if $old_metaclass; + $self->_force_rebless_instance($_[1]); - my $meta_instance = $self->get_meta_instance; + return $instance; +} - # we use $_[1] here because of t/306_rebless_overload.t regressions on 5.8.8 - $meta_instance->rebless_instance_structure( $_[1], $self ); +sub rebless_instance_away { + # this intentionally does nothing, it is just a hook +} - for my $attr ( $old_metaclass->get_all_attributes ) { - next if $self->has_attribute( $attr->name ); +sub _fixup_attributes_after_rebless { + my $self = shift; + my ($instance, $rebless_from, %params) = @_; + my $meta_instance = $self->get_meta_instance; + + for my $attr ( $rebless_from->get_all_attributes ) { + next if $self->find_attribute_by_name( $attr->name ); $meta_instance->deinitialize_slot( $instance, $_ ) for $attr->slots; } - return $instance; -} + foreach my $attr ( $self->get_all_attributes ) { + if ( $attr->has_value($instance) ) { + if ( defined( my $init_arg = $attr->init_arg ) ) { + $params{$init_arg} = $attr->get_value($instance) + unless exists $params{$init_arg}; + } + else { + $attr->set_value($instance, $attr->get_value($instance)); + } + } + } -sub rebless_instance_away { - # this intentionally does nothing, it is just a hook + foreach my $attr ($self->get_all_attributes) { + $attr->initialize_instance_slot($meta_instance, $instance, \%params); + } } sub _attach_attribute { @@ -757,10 +832,13 @@ sub get_all_attributes { sub superclasses { my $self = shift; - my $var_spec = { sigil => '@', type => 'ARRAY', name => 'ISA' }; + + my $isa = $self->get_or_add_package_symbol( + { sigil => '@', type => 'ARRAY', name => 'ISA' } ); + if (@_) { my @supers = @_; - @{$self->get_package_symbol($var_spec)} = @supers; + @{$isa} = @supers; # NOTE: # on 5.8 and below, we need to call @@ -779,12 +857,23 @@ sub superclasses { $self->_check_metaclass_compatibility(); $self->_superclasses_updated(); } - @{$self->get_package_symbol($var_spec)}; + + return @{$isa}; } sub _superclasses_updated { my $self = shift; $self->update_meta_instance_dependencies(); + # keep strong references to all our parents, so they don't disappear if + # they are anon classes and don't have any direct instances + $self->_superclass_metas( + map { Class::MOP::class_of($_) } $self->superclasses + ); +} + +sub _superclass_metas { + my $self = shift; + $self->{_superclass_metas} = [@_]; } sub subclasses { @@ -939,8 +1028,7 @@ sub get_all_methods { for my $class ( reverse $self->linearized_isa ) { my $meta = Class::MOP::Class->initialize($class); - $methods{$_} = $meta->get_method($_) - for $meta->get_method_list; + $methods{ $_->name } = $_ for $meta->_get_local_methods; } return values %methods; @@ -1153,27 +1241,14 @@ sub _immutable_metaclass { # metaclass roles applied (via Moose), then we want to make sure # that we preserve that anonymous class (see Fey::ORM for an # example of where this matters). - my $meta_name - = $meta->is_immutable - ? $meta->_get_mutable_metaclass_name - : ref $meta; + my $meta_name = $meta->_real_ref_name; my $immutable_meta = $meta_name->create( $class_name, superclasses => [ ref $self ], ); - Class::MOP::load_class($trait); - for my $meth ( Class::MOP::Class->initialize($trait)->get_all_methods ) { - my $meth_name = $meth->name; - - if ( $immutable_meta->find_method_by_name( $meth_name ) ) { - $immutable_meta->add_around_method_modifier( $meth_name, $meth->body ); - } - else { - $immutable_meta->add_method( $meth_name, $meth->clone ); - } - } + Class::MOP::MiniTrait::apply( $immutable_meta, $trait ); $immutable_meta->make_immutable( inline_constructor => 0, @@ -1397,6 +1472,27 @@ hash reference are method names and values are subroutine references. An optional array reference of L objects. +=item * meta_name + +Specifies the name to install the C method for this class under. +If it is not passed, C is assumed, and if C is explicitly +given, no meta method will be installed. + +=item * weaken + +If true, the metaclass that is stored in the global cache will be a +weak reference. + +Classes created in this way are destroyed once the metaclass they are +attached to goes out of scope, and will be removed from Perl's internal +symbol table. + +All instances of a class with a weakened metaclass keep a special +reference to the metaclass object, which prevents the metaclass from +going out of scope while any instances exist. + +This only works if the instance is based on a hash reference, however. + =back =item B<< Class::MOP::Class->create_anon_class(%options) >> @@ -1408,15 +1504,8 @@ that name is a unique name generated internally by this module. It accepts the same C, C, and C parameters that C accepts. -Anonymous classes are destroyed once the metaclass they are attached -to goes out of scope, and will be removed from Perl's internal symbol -table. - -All instances of an anonymous class keep a special reference to the -metaclass object, which prevents the metaclass from going out of scope -while any instances exist. - -This only works if the instance is based on a hash reference, however. +Anonymous classes default to C<< weaken => 1 >>, although this can be +overridden. =item B<< Class::MOP::Class->initialize($package_name, %options) >> @@ -1508,6 +1597,13 @@ metaclass. Returns an instance of the C to be used in the construction of a new instance of the class. +=item B<< $metaclass->inline_create_instance($class_var) >> + +=item B<< $metaclass->inline_rebless_instance($instance_var, $class_var) >> + +These methods takes variable names, and use them to create an inline snippet +of code that will create a new instance of the class. + =back =head2 Informational predicates @@ -1706,7 +1802,8 @@ classes. =item B<< $metaclass->get_attribute_list >> This will return a list of attributes I for all attributes -defined in this class. +defined in this class. Note that this operates on the current class +only, it does not traverse the inheritance hierarchy. =item B<< $metaclass->get_all_attributes >>