From: Stevan Little Date: Thu, 9 Nov 2006 22:43:09 +0000 (+0000) Subject: IT WORKS NOWrun_testsrun_testsrun_testsrun_tests X-Git-Tag: 0_37_002~2 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=4d0489082d1147dc5f77a3a22b0c7a7a4703b084;p=gitmo%2FClass-MOP.git IT WORKS NOWrun_testsrun_testsrun_testsrun_tests --- diff --git a/lib/Class/MOP/Class.pm b/lib/Class/MOP/Class.pm index 0a16c25..bf92bf2 100644 --- a/lib/Class/MOP/Class.pm +++ b/lib/Class/MOP/Class.pm @@ -4,6 +4,7 @@ package Class::MOP::Class; use strict; use warnings; +use Class::MOP::Immutable; use Class::MOP::Instance; use Class::MOP::Method::Wrapped; @@ -729,14 +730,17 @@ sub is_mutable { 1 } sub is_immutable { 0 } { - use Class::MOP::Immutable; - - my $IMMUTABLE_META; - + # NOTE: + # the immutable version of a + # particular metaclass is + # really class-level data so + # we don't want to regenerate + # it any more than we need to + my $IMMUTABLE_METACLASS; sub make_immutable { my ($self) = @_; - $IMMUTABLE_META ||= Class::MOP::Immutable->new($self->meta, { + $IMMUTABLE_METACLASS ||= Class::MOP::Immutable->new($self, { read_only => [qw/superclasses/], cannot_call => [qw/ add_method @@ -753,9 +757,9 @@ sub is_immutable { 0 } get_meta_instance => 'SCALAR', get_method_map => 'SCALAR', } - })->create_immutable_metaclass; - - $IMMUTABLE_META->make_metaclass_immutable(@_); + }); + + $IMMUTABLE_METACLASS->make_metaclass_immutable(@_) } } diff --git a/lib/Class/MOP/Class/Immutable.pm b/lib/Class/MOP/Class/Immutable.pm deleted file mode 100644 index aa9ad68..0000000 --- a/lib/Class/MOP/Class/Immutable.pm +++ /dev/null @@ -1,262 +0,0 @@ - -package Class::MOP::Class::Immutable; - -use strict; -use warnings; - -use Class::MOP::Method::Constructor; - -use Carp 'confess'; -use Scalar::Util 'blessed'; - -our $VERSION = '0.04'; -our $AUTHORITY = 'cpan:STEVAN'; - -use base 'Class::MOP::Class'; - -# enforce the meta-circularity here -# and hide the Immutable part - -sub meta { - my $self = shift; - # if it is not blessed, then someone is asking - # for the meta of Class::MOP::Class::Immutable - return Class::MOP::Class->initialize($self) unless blessed($self); - # otherwise, they are asking for the metaclass - # which has been made immutable, which is itself - return $self; -} - -# methods which can *not* be called -for my $meth (qw( - add_method - alias_method - remove_method - add_attribute - remove_attribute - add_package_symbol - remove_package_symbol -)) { - no strict 'refs'; - *{$meth} = sub { - confess "Cannot call method '$meth' on an immutable instance"; - }; -} - -# NOTE: -# superclasses is an accessor, so -# it just cannot be changed -sub superclasses { - my $class = shift; - (!@_) || confess 'Cannot change the "superclasses" on an immmutable instance'; - @{$class->get_package_symbol('@ISA')}; -} - -# predicates - -sub is_mutable { 0 } -sub is_immutable { 1 } - -sub make_immutable { () } - -sub make_metaclass_immutable { - my ($class, $metaclass, %options) = @_; - - # NOTE: - # i really need the // (defined-or) operator here - $options{inline_accessors} = 1 unless exists $options{inline_accessors}; - $options{inline_constructor} = 1 unless exists $options{inline_constructor}; - $options{constructor_name} = 'new' unless exists $options{constructor_name}; - $options{debug} = 0 unless exists $options{debug}; - - my $meta_instance = $metaclass->get_meta_instance; - $metaclass->{'___class_precedence_list'} = [ $metaclass->class_precedence_list ]; - $metaclass->{'___compute_all_applicable_attributes'} = [ $metaclass->compute_all_applicable_attributes ]; - $metaclass->{'___get_meta_instance'} = $meta_instance; - $metaclass->{'___original_class'} = blessed($metaclass); - - if ($options{inline_accessors}) { - foreach my $attr_name ($metaclass->get_attribute_list) { - # inline the accessors - $metaclass->get_attribute($attr_name) - ->install_accessors(1); - } - } - - if ($options{inline_constructor}) { - my $constructor_class = $options{constructor_class} || 'Class::MOP::Method::Constructor'; - $metaclass->add_method( - $options{constructor_name}, - $constructor_class->new( - options => \%options, - meta_instance => $meta_instance, - attributes => $metaclass->{'___compute_all_applicable_attributes'} - ) - ); - } - - # now cache the method map ... - $metaclass->{'___get_method_map'} = $metaclass->get_method_map; - - bless $metaclass => $class; -} - -# cached methods - -sub get_meta_instance { (shift)->{'___get_meta_instance'} } -sub class_precedence_list { @{(shift)->{'___class_precedence_list'}} } -sub compute_all_applicable_attributes { @{(shift)->{'___compute_all_applicable_attributes'}} } -sub get_mutable_metaclass_name { (shift)->{'___original_class'} } -sub get_method_map { (shift)->{'___get_method_map'} } - -1; - -__END__ - -=pod - -=head1 NAME - -Class::MOP::Class::Immutable - An immutable version of Class::MOP::Class - -=head1 SYNOPSIS - - package Point; - use metaclass; - - __PACKAGE__->meta->add_attribute('x' => (accessor => 'x', default => 10)); - __PACKAGE__->meta->add_attribute('y' => (accessor => 'y')); - - sub new { - my $class = shift; - $class->meta->new_object(@_); - } - - sub clear { - my $self = shift; - $self->x(0); - $self->y(0); - } - - __PACKAGE__->meta->make_immutable(); # close the class - -=head1 DESCRIPTION - -Class::MOP offers many benefits to object oriented development but it -comes at a cost. Pure Class::MOP classes can be quite a bit slower than -the typical hand coded Perl classes. This is because just about -I is recalculated on the fly, and nothing is cached. The -reason this is so, is because Perl itself allows you to modify virtually -everything at runtime. Class::MOP::Class::Immutable offers an alternative -to this. - -By making your class immutable, you are promising that you will not -modify your inheritence tree or the attributes of any classes in -that tree. Since runtime modifications like this are fairly atypical -(and usually recomended against), this is not usally a very hard promise -to make. For making this promise you are given a wide range of -optimization options which bring speed close to (and sometimes above) -those of typical hand coded Perl. - -=head1 METHODS - -=over 4 - -=item B - -This will return a B instance which is related -to this class. - -=back - -=head2 Introspection and Construction - -=over 4 - -=item B - -The arguments to C are passed -to this method, which - -=over 4 - -=item I - -=item I - -=item I - -=item I - -=back - -=item B - -=item B - -=item B - -=item B - -=back - -=head2 Methods which will die if you touch them. - -=over 4 - -=item B - -=item B - -=item B - -=item B - -=item B - -=item B - -=item B - -=back - -=head2 Methods which work slightly differently. - -=over 4 - -=item B - -This method becomes read-only in an immutable class. - -=back - -=head2 Cached methods - -=over 4 - -=item B - -=item B - -=item B - -=item B - -=back - -=head1 AUTHORS - -Stevan Little Estevan@iinteractive.comE - -Yuval Kogman Enothingmuch@woobling.comE - -=head1 COPYRIGHT AND LICENSE - -Copyright 2006 by Infinity Interactive, Inc. - -L - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut diff --git a/lib/Class/MOP/Immutable.pm b/lib/Class/MOP/Immutable.pm new file mode 100644 index 0000000..ad8b08d --- /dev/null +++ b/lib/Class/MOP/Immutable.pm @@ -0,0 +1,217 @@ + +package Class::MOP::Immutable; + +use strict; +use warnings; + +use Class::MOP::Method::Constructor; + +use Carp 'confess'; +use Scalar::Util 'blessed'; + +our $VERSION = '0.01'; +our $AUTHORITY = 'cpan:STEVAN'; + +sub new { + my ($class, $metaclass, $options) = @_; + + my $self = bless { + metaclass => $metaclass, + options => $options, + immutable_metaclass => undef, + } => $class; + + # NOTE: + # we initialize the immutable + # version of the metaclass here + $self->create_immutable_metaclass; + + return $self; +} + +sub immutable_metaclass { (shift)->{immutable_metaclass} } +sub metaclass { (shift)->{metaclass} } +sub options { (shift)->{options} } + +sub create_immutable_metaclass { + my $self = shift; + + # NOTE: + # The immutable version of the + # metaclass is just a anon-class + # which shadows the methods + # appropriately + $self->{immutable_metaclass} = Class::MOP::Class->create_anon_class( + superclasses => [ blessed($self->metaclass) ], + methods => $self->create_methods_for_immutable_metaclass, + ); +} + +my %DEFAULT_METHODS = ( + meta => sub { + my $self = shift; + # if it is not blessed, then someone is asking + # for the meta of Class::MOP::Class::Immutable + return Class::MOP::Class->initialize($self) unless blessed($self); + # otherwise, they are asking for the metaclass + # which has been made immutable, which is itself + return $self; + }, + is_mutable => sub { 0 }, + is_immutable => sub { 1 }, + make_immutable => sub { ( ) }, +); + +# NOTE: +# this will actually convert the +# existing metaclass to an immutable +# version of itself +sub make_metaclass_immutable { + my ($self, $metaclass, %options) = @_; + + $options{inline_accessors} = 1 unless exists $options{inline_accessors}; + $options{inline_constructor} = 1 unless exists $options{inline_constructor}; + $options{constructor_name} = 'new' unless exists $options{constructor_name}; + $options{debug} = 0 unless exists $options{debug}; + + if ($options{inline_accessors}) { + foreach my $attr_name ($metaclass->get_attribute_list) { + # inline the accessors + $metaclass->get_attribute($attr_name) + ->install_accessors(1); + } + } + + if ($options{inline_constructor}) { + my $constructor_class = $options{constructor_class} || 'Class::MOP::Method::Constructor'; + + my $constructor = $constructor_class->new( + options => \%options, + meta_instance => $metaclass->get_meta_instance, + attributes => [ $metaclass->compute_all_applicable_attributes ] + ); + + $metaclass->add_method( + $options{constructor_name}, + $constructor + ); + } + + my $memoized_methods = $self->options->{memoize}; + foreach my $method_name (keys %{$memoized_methods}) { + my $type = $memoized_methods->{$method_name}; + + ($metaclass->can($method_name)) + || confess "Could not find the method '$method_name' in " . $metaclass->name; + + my $memoized_method; + if ($type eq 'ARRAY') { + $metaclass->{'___' . $method_name} = [ $metaclass->$method_name ]; + } + elsif ($type eq 'HASH') { + $metaclass->{'___' . $method_name} = { $metaclass->$method_name }; + } + elsif ($type eq 'SCALAR') { + $metaclass->{'___' . $method_name} = $metaclass->$method_name; + } + } + $metaclass->{'___original_class'} = blessed($metaclass); + + bless $metaclass => $self->immutable_metaclass->name; +} + +sub create_methods_for_immutable_metaclass { + my $self = shift; + + my %methods = %DEFAULT_METHODS; + + foreach my $read_only_method (@{$self->options->{read_only}}) { + my $method = $self->metaclass->meta->find_method_by_name($read_only_method); + + (defined $method) + || confess "Could not find the method '$read_only_method' in " . $self->metaclass->name; + + $methods{$read_only_method} = sub { + confess "This method is read-only" if scalar @_ > 1; + goto &{$method->body} + }; + } + + foreach my $cannot_call_method (@{$self->options->{cannot_call}}) { + $methods{$cannot_call_method} = sub { + confess "This method cannot be called on an immutable instance"; + }; + } + + my $memoized_methods = $self->options->{memoize}; + + foreach my $method_name (keys %{$memoized_methods}) { + my $type = $memoized_methods->{$method_name}; + if ($type eq 'ARRAY') { + $methods{$method_name} = sub { @{$_[0]->{'___' . $method_name}} }; + } + elsif ($type eq 'HASH') { + $methods{$method_name} = sub { %{$_[0]->{'___' . $method_name}} }; + } + elsif ($type eq 'SCALAR') { + $methods{$method_name} = sub { $_[0]->{'___' . $method_name} }; + } + } + + $methods{get_mutable_metaclass_name} = sub { (shift)->{'___original_class'} }; + + return \%methods; +} + +1; + +__END__ + +=pod + +=head1 NAME + +Class::MOP::Immutable - A class to transform Class::MOP::Class metaclasses + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +=head1 METHODS + +=over 4 + +=item B + +=item B + +=item B + +=item B + +=back + +=over 4 + +=item B + +=item B + +=item B + +=back + +=head1 AUTHORS + +Stevan Little Estevan@iinteractive.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Class/MOP/Instance.pm b/lib/Class/MOP/Instance.pm index 764a39c..89ea9c8 100644 --- a/lib/Class/MOP/Instance.pm +++ b/lib/Class/MOP/Instance.pm @@ -244,6 +244,8 @@ we will add then when we need them basically. =over 4 +=item B + =item B This will return the current list of slots based on what was diff --git a/lib/Class/MOP/Method/Constructor.pm b/lib/Class/MOP/Method/Constructor.pm index f420fb3..2d4c99a 100644 --- a/lib/Class/MOP/Method/Constructor.pm +++ b/lib/Class/MOP/Method/Constructor.pm @@ -85,7 +85,7 @@ sub intialize_body { $code = eval $source; confess "Could not eval the constructor :\n\n$source\n\nbecause :\n\n$@" if $@; } - $self->{body} = $code; + $self->{'&!body'} = $code; } sub _generate_slot_initializer { diff --git a/t/000_load.t b/t/000_load.t index b3e27b9..324ff6b 100644 --- a/t/000_load.t +++ b/t/000_load.t @@ -10,7 +10,7 @@ BEGIN { use_ok('Class::MOP::Package'); use_ok('Class::MOP::Module'); use_ok('Class::MOP::Class'); - use_ok('Class::MOP::Class::Immutable'); + use_ok('Class::MOP::Immutable'); use_ok('Class::MOP::Attribute'); use_ok('Class::MOP::Method'); use_ok('Class::MOP::Method::Wrapped'); @@ -22,6 +22,8 @@ BEGIN { # make sure we are tracking metaclasses correctly +my $CLASS_MOP_CLASS_IMMUTABLE_CLASS = 'Class::MOP::Class::__ANON__::SERIAL::1'; + my %METAS = ( 'Class::MOP::Attribute' => Class::MOP::Attribute->meta, 'Class::MOP::Method::Accessor' => Class::MOP::Method::Accessor->meta, @@ -32,14 +34,17 @@ my %METAS = ( 'Class::MOP::Method' => Class::MOP::Method->meta, 'Class::MOP::Method::Wrapped' => Class::MOP::Method::Wrapped->meta, 'Class::MOP::Instance' => Class::MOP::Instance->meta, - 'Class::MOP::Object' => Class::MOP::Object->meta, + 'Class::MOP::Object' => Class::MOP::Object->meta, ); ok($_->is_immutable(), '... ' . $_->name . ' is immutable') for values %METAS; is_deeply( { Class::MOP::get_all_metaclasses }, - \%METAS, + { + %METAS, + $CLASS_MOP_CLASS_IMMUTABLE_CLASS => $CLASS_MOP_CLASS_IMMUTABLE_CLASS->meta + }, '... got all the metaclasses'); is_deeply( @@ -47,6 +52,7 @@ is_deeply( [ Class::MOP::Attribute->meta, Class::MOP::Class->meta, + $CLASS_MOP_CLASS_IMMUTABLE_CLASS->meta, Class::MOP::Instance->meta, Class::MOP::Method->meta, Class::MOP::Method::Accessor->meta, @@ -54,13 +60,13 @@ is_deeply( Class::MOP::Method::Wrapped->meta, Class::MOP::Module->meta, Class::MOP::Object->meta, - Class::MOP::Package->meta, + Class::MOP::Package->meta, ], '... got all the metaclass instances'); is_deeply( [ sort { $a cmp $b } Class::MOP::get_all_metaclass_names() ], - [ qw/ + [ sort qw/ Class::MOP::Attribute Class::MOP::Class Class::MOP::Instance @@ -71,7 +77,7 @@ is_deeply( Class::MOP::Module Class::MOP::Object Class::MOP::Package - / ], + /, $CLASS_MOP_CLASS_IMMUTABLE_CLASS ], '... got all the metaclass names'); is_deeply( @@ -79,6 +85,7 @@ is_deeply( [ "Class::MOP::Attribute-" . $Class::MOP::Attribute::VERSION . "-cpan:STEVAN", "Class::MOP::Class-" . $Class::MOP::Class::VERSION . "-cpan:STEVAN", + $CLASS_MOP_CLASS_IMMUTABLE_CLASS, "Class::MOP::Instance-" . $Class::MOP::Instance::VERSION . "-cpan:STEVAN", "Class::MOP::Method-" . $Class::MOP::Method::VERSION . "-cpan:STEVAN", "Class::MOP::Method::Accessor-" . $Class::MOP::Method::Accessor::VERSION . "-cpan:STEVAN", diff --git a/t/018_anon_class.t b/t/018_anon_class.t index 1eb3aa6..ad048eb 100644 --- a/t/018_anon_class.t +++ b/t/018_anon_class.t @@ -49,7 +49,7 @@ my $instance; ok($anon_class->has_method('foo'), '... we have a foo method now'); $instance = $anon_class->new_object(); - isa_ok($instance, $anon_class->name); + isa_ok($instance, $anon_class->name); isa_ok($instance, 'Foo'); is($instance->foo, '__ANON__::foo', '... got the right return value of our foo method'); diff --git a/t/070_immutable_metaclass.t b/t/070_immutable_metaclass.t index d057136..5b1a1ca 100644 --- a/t/070_immutable_metaclass.t +++ b/t/070_immutable_metaclass.t @@ -3,12 +3,11 @@ use strict; use warnings; -use Test::More tests => 77; +use Test::More tests => 73; use Test::Exception; BEGIN { use_ok('Class::MOP'); - use_ok('Class::MOP::Class::Immutable'); } { @@ -57,7 +56,6 @@ BEGIN { ok(!$meta->is_mutable, '... our class is no longer mutable'); ok($meta->is_immutable, '... our class is now immutable'); - isa_ok($meta, 'Class::MOP::Class::Immutable'); isa_ok($meta, 'Class::MOP::Class'); dies_ok { $meta->add_method() } '... exception thrown as expected'; @@ -119,7 +117,6 @@ BEGIN { ok(!$meta->is_mutable, '... our class is no longer mutable'); ok($meta->is_immutable, '... our class is now immutable'); - isa_ok($meta, 'Class::MOP::Class::Immutable'); isa_ok($meta, 'Class::MOP::Class'); dies_ok { $meta->add_method() } '... exception thrown as expected'; @@ -181,7 +178,6 @@ BEGIN { ok(!$meta->is_mutable, '... our class is no longer mutable'); ok($meta->is_immutable, '... our class is now immutable'); - isa_ok($meta, 'Class::MOP::Class::Immutable'); isa_ok($meta, 'Class::MOP::Class'); dies_ok { $meta->add_method() } '... exception thrown as expected'; diff --git a/t/072_immutable_w_constructors.t b/t/072_immutable_w_constructors.t index aeeaff6..b8a07db 100644 --- a/t/072_immutable_w_constructors.t +++ b/t/072_immutable_w_constructors.t @@ -3,7 +3,7 @@ use strict; use warnings; -use Test::More tests => 76; +use Test::More tests => 73; use Test::Exception; BEGIN { @@ -72,7 +72,6 @@ BEGIN { } '... changed Foo to be immutable'; ok($meta->is_immutable, '... our class is now immutable'); - isa_ok($meta, 'Class::MOP::Class::Immutable'); isa_ok($meta, 'Class::MOP::Class'); # they made a constructor for us :) @@ -128,7 +127,6 @@ BEGIN { } '... changed Bar to be immutable'; ok($meta->is_immutable, '... our class is now immutable'); - isa_ok($meta, 'Class::MOP::Class::Immutable'); isa_ok($meta, 'Class::MOP::Class'); # they made a constructor for us :) @@ -198,7 +196,6 @@ BEGIN { } '... changed Bar to be immutable'; ok($meta->is_immutable, '... our class is now immutable'); - isa_ok($meta, 'Class::MOP::Class::Immutable'); isa_ok($meta, 'Class::MOP::Class'); ok(!Baz->meta->has_method('new'), '... no constructor was made');