lots of more refactored goodness in the TC system
Stevan Little [Sat, 15 Sep 2007 21:18:58 +0000 (21:18 +0000)]
Changes
MANIFEST
lib/Moose/Meta/TypeCoercion/Union.pm [new file with mode: 0644]
lib/Moose/Meta/TypeConstraint.pm
lib/Moose/Meta/TypeConstraint/Union.pm
lib/Moose/Util/TypeConstraints.pm
t/040_type_constraints/008_union_types.t

diff --git a/Changes b/Changes
index d2726f2..b22d3ce 100644 (file)
--- a/Changes
+++ b/Changes
@@ -20,6 +20,21 @@ Revision history for Perl extension Moose
         inner/augment (mst)
         - added tests for this (eilara)
 
+    * Moose::Meta::Attribute
+      Moose::Meta::Method::Constructor
+      Moose::Meta::Method::Accessor
+      - fixed issue with overload::Overloaded getting called 
+        on non-blessed items.
+        - added tests for this
+    
+    * Moose::Coookbook::Snacks
+      - these are bits of documentation, not quite as big as 
+        Recipes but which have no clear place in the module docs. 
+        So they are Snacks! (horray for castaway++)
+
+    +++ Major Refactor of the Type Constraint system +++
+    +++       with new features added as well        +++
+
     * Moose::Util::TypeConstraint
       - no longer uses package variable to keep track of 
         the type constraints, now uses the an instance of
@@ -31,8 +46,13 @@ Revision history for Perl extension Moose
         can track where the type constraints are created
       
     * Moose::Meta::TypeConstraint::Union
-      - this is not a subclass of Moose::Meta::TypeConstraint      
-        which is more correct
+      - this is now been refactored to be a subclass of 
+        Moose::Meta::TypeConstraint
+        
+    * Moose::Meta::TypeCoercion::Union
+      - this has been added to service the newly refactored
+        Moose::Meta::TypeConstraint::Union and is itself 
+        a subclass of Moose::Meta::TypeCoercion
       
     * Moose::Meta::TypeConstraint::Container
       - added this module (taken from MooseX::AttributeHelpers)
@@ -41,18 +61,6 @@ Revision history for Perl extension Moose
     
     * Moose::Meta::TypeConstraint::Registry
       - added this class to keep track of type constraints
-    
-    * Moose::Meta::Attribute
-      Moose::Meta::Method::Constructor
-      Moose::Meta::Method::Accessor
-      - fixed issue with overload::Overloaded getting called 
-        on non-blessed items.
-        - added tests for this
-    
-    * Moose::Coookbook::Snacks
-      - these are bits of documentation, not quite as big as 
-        Recipes but which have no clear place in the module docs. 
-        So they are Snacks! (horray for castaway++)
 
 0.25 Mon. Aug. 13, 2007
     * Moose
index 556767e..633b98a 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -26,6 +26,7 @@ lib/Moose/Meta/Instance.pm
 lib/Moose/Meta/Method.pm
 lib/Moose/Meta/Role.pm
 lib/Moose/Meta/TypeCoercion.pm
+lib/Moose/Meta/TypeCoercion/Union.pm
 lib/Moose/Meta/TypeConstraint.pm
 lib/Moose/Meta/Method/Accessor.pm
 lib/Moose/Meta/Method/Constructor.pm
diff --git a/lib/Moose/Meta/TypeCoercion/Union.pm b/lib/Moose/Meta/TypeCoercion/Union.pm
new file mode 100644 (file)
index 0000000..31446f4
--- /dev/null
@@ -0,0 +1,93 @@
+
+package Moose::Meta::TypeCoercion::Union;
+
+use strict;
+use warnings;
+use metaclass;
+
+use Carp         'confess';
+use Scalar::Util 'blessed';
+
+our $VERSION   = '0.01';
+our $AUTHORITY = 'cpan:STEVAN';
+
+use base 'Moose::Meta::TypeCoercion';
+
+sub compile_type_coercion {
+    my $self            = shift;
+    my $type_constraint = $self->type_constraint;
+    
+    (blessed $type_constraint && $type_constraint->isa('Moose::Meta::TypeConstraint::Union'))
+     || confess "You can only a Moose::Meta::TypeCoercion::Union for a " .
+                "Moose::Meta::TypeConstraint::Union, not a $type_constraint";
+    
+    $self->_compiled_type_coercion(sub {
+        my $value = shift;
+        # go through all the type constraints 
+        # in the union, and check em ...
+        foreach my $type (@{$type_constraint->type_constraints}) {
+            # if they have a coercion first
+            if ($type->has_coercion) {
+                # then try to coerce them ...
+                my $temp = $type->coerce($value);
+                # and if they get something 
+                # make sure it still fits within
+                # the union type ...
+                return $temp if $type_constraint->check($temp);
+            }
+        }
+        return undef;    
+    });
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::Meta::TypeCoercion::Union - The Moose Type Coercion metaclass for Unions
+
+=head1 DESCRIPTION
+
+For the most part, the only time you will ever encounter an 
+instance of this class is if you are doing some serious deep 
+introspection. This API should not be considered final, but 
+it is B<highly unlikely> that this will matter to a regular 
+Moose user.
+
+If you wish to use features at this depth, please come to the 
+#moose IRC channel on irc.perl.org and we can talk :)
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=item B<compile_type_coercion>
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no 
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2006, 2007 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself. 
+
+=cut
\ No newline at end of file
index 47cf32d..054e7fe 100644 (file)
@@ -15,11 +15,11 @@ use Scalar::Util 'blessed';
 our $VERSION   = '0.09';
 our $AUTHORITY = 'cpan:STEVAN';
 
-use Moose::Meta::TypeConstraint::Union;
-use Moose::Meta::TypeConstraint::Container;
-
-__PACKAGE__->meta->add_attribute('name'       => (reader => 'name'      ));
-__PACKAGE__->meta->add_attribute('parent'     => (reader => 'parent'    ));
+__PACKAGE__->meta->add_attribute('name'       => (reader => 'name'));
+__PACKAGE__->meta->add_attribute('parent'     => (
+    reader    => 'parent',
+    predicate => 'has_parent',
+));
 __PACKAGE__->meta->add_attribute('constraint' => (
     reader => 'constraint',
     writer => '_set_constraint',
@@ -32,18 +32,18 @@ __PACKAGE__->meta->add_attribute('coercion'   => (
     accessor  => 'coercion',
     predicate => 'has_coercion'
 ));
-
-# private accessor
-__PACKAGE__->meta->add_attribute('compiled_type_constraint' => (
-    accessor => '_compiled_type_constraint'
-));
-
 __PACKAGE__->meta->add_attribute('hand_optimized_type_constraint' => (
     init_arg  => 'optimized',
     accessor  => 'hand_optimized_type_constraint',
     predicate => 'has_hand_optimized_type_constraint',    
 ));
 
+# private accessors
+
+__PACKAGE__->meta->add_attribute('compiled_type_constraint' => (
+    accessor  => '_compiled_type_constraint',
+    predicate => '_has_compiled_type_constraint'
+));
 __PACKAGE__->meta->add_attribute('package_defined_in' => (
     accessor => '_package_defined_in'
 ));
@@ -51,79 +51,13 @@ __PACKAGE__->meta->add_attribute('package_defined_in' => (
 sub new { 
     my $class = shift;
     my $self  = $class->meta->new_object(@_);
-    $self->compile_type_constraint();
+    $self->compile_type_constraint()
+        unless $self->_has_compiled_type_constraint;
     return $self;
 }
 
-sub coerce { 
-    ((shift)->coercion || confess "Cannot coerce without a type coercion")->coerce(@_) 
-}
-
-sub _collect_all_parents {
-    my $self = shift;
-    my @parents;
-    my $current = $self->parent;
-    while (defined $current) {
-        push @parents => $current;
-        $current = $current->parent;
-    }
-    return @parents;
-}
-
-sub compile_type_constraint {
-    my $self  = shift;
-    
-    if ($self->has_hand_optimized_type_constraint) {
-        my $type_constraint = $self->hand_optimized_type_constraint;
-        $self->_compiled_type_constraint(sub {
-            return undef unless $type_constraint->($_[0]);
-            return 1;
-        });
-        return;
-    }
-    
-    my $check = $self->constraint;
-    (defined $check)
-        || confess "Could not compile type constraint '" . $self->name . "' because no constraint check";
-    my $parent = $self->parent;
-    if (defined $parent) {
-        # we have a subtype ...    
-        # so we gather all the parents in order
-        # and grab their constraints ...
-        my @parents;
-        foreach my $parent ($self->_collect_all_parents) {
-            if ($parent->has_hand_optimized_type_constraint) {
-                unshift @parents => $parent->hand_optimized_type_constraint;
-                last;                
-            }
-            else {
-                unshift @parents => $parent->constraint;
-            }
-        }
-        
-        # then we compile them to run without
-        # having to recurse as we did before
-               $self->_compiled_type_constraint(subname $self->name => sub {                   
-                       local $_ = $_[0];
-            foreach my $parent (@parents) {
-                return undef unless $parent->($_[0]);
-            }
-                       return undef unless $check->($_[0]);
-                       1;
-               });               
-    }
-    else {
-        # we have a type ....
-       $self->_compiled_type_constraint(subname $self->name => sub { 
-               local $_ = $_[0];
-               return undef unless $check->($_[0]);
-               1;
-       });
-    }
-}
-
-sub check { $_[0]->_compiled_type_constraint->($_[1]) }
-
+sub coerce   { ((shift)->coercion || confess "Cannot coerce without a type coercion")->coerce(@_) }
+sub check    { $_[0]->_compiled_type_constraint->($_[1]) }
 sub validate { 
     my ($self, $value) = @_;
     if ($self->_compiled_type_constraint->($value)) {
@@ -140,6 +74,8 @@ sub validate {
     }
 }
 
+## type predicates ...
+
 sub is_a_type_of {
     my ($self, $type_name) = @_;
     ($self->name eq $type_name || $self->is_subtype_of($type_name));
@@ -155,18 +91,98 @@ sub is_subtype_of {
     return 0;
 }
 
-sub union {
-    my ($class, @type_constraints) = @_;
-    (scalar @type_constraints >= 2)
-        || confess "You must pass in at least 2 Moose::Meta::TypeConstraint instances to make a union";    
-    (blessed($_) && $_->isa('Moose::Meta::TypeConstraint'))
-        || confess "You must pass in only Moose::Meta::TypeConstraint instances to make unions"
-            foreach @type_constraints;
-    return Moose::Meta::TypeConstraint::Union->new(
-        type_constraints => \@type_constraints,
-    );
+## compiling the type constraint
+
+sub compile_type_constraint {
+    my $self = shift;
+    $self->_compiled_type_constraint($self->_actually_compile_type_constraint);
 }
 
+## type compilers ...
+
+sub _actually_compile_type_constraint {
+    my $self = shift;
+    
+    return $self->_compile_hand_optimized_type_constraint
+        if $self->has_hand_optimized_type_constraint;
+    
+    my $check = $self->constraint;
+    (defined $check)
+        || confess "Could not compile type constraint '" 
+                . $self->name 
+                . "' because no constraint check";
+                
+    return $self->_compile_subtype($check)
+        if $self->has_parent;
+        
+    return $self->_compile_type($check);
+}
+
+sub _compile_hand_optimized_type_constraint {
+    my $self = shift;
+    
+    my $type_constraint = $self->hand_optimized_type_constraint;
+    
+    return sub {
+        return undef unless $type_constraint->($_[0]);
+        return 1;
+    };    
+}
+
+sub _compile_subtype {
+    my ($self, $check) = @_;
+    
+    # so we gather all the parents in order
+    # and grab their constraints ...
+    my @parents;
+    foreach my $parent ($self->_collect_all_parents) {
+        if ($parent->has_hand_optimized_type_constraint) {
+            unshift @parents => $parent->hand_optimized_type_constraint;
+            last;                
+        }
+        else {
+            unshift @parents => $parent->constraint;
+        }
+    }
+    
+    # then we compile them to run without
+    # having to recurse as we did before
+       return subname $self->name => sub {                     
+               local $_ = $_[0];
+        foreach my $parent (@parents) {
+            return undef unless $parent->($_[0]);
+        }
+               return undef unless $check->($_[0]);
+               1;
+       };    
+}
+
+sub _compile_type {
+    my ($self, $check) = @_;
+       return subname $self->name => sub { 
+               local $_ = $_[0];
+               return undef unless $check->($_[0]);
+               1;
+       };    
+}
+
+## other utils ...
+
+sub _collect_all_parents {
+    my $self = shift;
+    my @parents;
+    my $current = $self->parent;
+    while (defined $current) {
+        push @parents => $current;
+        $current = $current->parent;
+    }
+    return @parents;
+}
+
+## this should get deprecated actually ...
+
+sub union { die "DEPRECATED" }
+
 1;
 
 __END__
@@ -225,6 +241,8 @@ the C<message> will be used to construct a custom error message.
 
 =item B<parent>
 
+=item B<has_parent>
+
 =item B<constraint>
 
 =item B<has_message>
@@ -241,9 +259,14 @@ the C<message> will be used to construct a custom error message.
 
 =back
 
+=head2 DEPRECATED METHOD
+
 =over 4
 
-=item B<union (@type_constraints)>
+=item B<union>
+
+This was just bad idea on my part,.. use the L<Moose::Meta::TypeConstraint::Union>
+itself instead.
 
 =back
 
index 25fe9e6..5e1fbd1 100644 (file)
@@ -5,16 +5,11 @@ use strict;
 use warnings;
 use metaclass;
 
+use Moose::Meta::TypeCoercion::Union;
+
 our $VERSION   = '0.06';
 our $AUTHORITY = 'cpan:STEVAN';
 
-# NOTE:
-# this is not really correct, but 
-# I think it shoul be here anyway.
-# In truth, this should implement 
-# the same abstract base/interface
-# as the TC moule.
-# - SL
 use base 'Moose::Meta::TypeConstraint';
 
 __PACKAGE__->meta->add_attribute('type_constraints' => (
@@ -23,76 +18,30 @@ __PACKAGE__->meta->add_attribute('type_constraints' => (
 ));
 
 sub new { 
-    my $class = shift;
-    my $self  = $class->meta->new_object(@_);
+    my ($class, %options) = @_;
+    my $self = $class->SUPER::new(
+        name     => (join ' | ' => map { $_->name } @{$options{type_constraints}}),
+        parent   => undef,
+        message  => undef,
+        hand_optimized_type_constraint => undef,
+        compiled_type_constraint => sub {
+            my $value = shift;
+            foreach my $type (@{$options{type_constraints}}) {
+                return 1 if $type->check($value);
+            }
+            return undef;    
+        },
+        %options
+    );
+    $self->_set_constraint(sub { $self->check($_[0]) });
+    $self->coercion(Moose::Meta::TypeCoercion::Union->new(
+        type_constraint => $self
+    ));
     return $self;
 }
 
-sub name { join ' | ' => map { $_->name } @{$_[0]->type_constraints} }
-
-# NOTE:
-# this should probably never be used
-# but we include it here for completeness
-sub constraint    { 
-    my $self = shift;
-    sub { $self->check($_[0]) }; 
-}
-
-# conform to the TypeConstraint API
-sub parent        { undef  }
-sub message       { undef  }
-sub has_message   { 0      }
-
-# FIXME:
-# not sure what this should actually do here
-sub coercion { undef  }
-
-# this should probably be memoized
-sub has_coercion  {
-    my $self  = shift;
-    foreach my $type (@{$self->type_constraints}) {
-        return 1 if $type->has_coercion
-    }
-    return 0;    
-}
-
-# NOTE:
-# this feels too simple, and may not always DWIM
-# correctly, especially in the presence of 
-# close subtype relationships, however it should 
-# work for a fair percentage of the use cases
-sub coerce { 
-    my $self  = shift;
-    my $value = shift;
-    foreach my $type (@{$self->type_constraints}) {
-        if ($type->has_coercion) {
-            my $temp = $type->coerce($value);
-            return $temp if $self->check($temp);
-        }
-    }
-    return undef;    
-}
-
-sub _compiled_type_constraint {
-    my $self  = shift;
-    return sub {
-        my $value = shift;
-        foreach my $type (@{$self->type_constraints}) {
-            return 1 if $type->check($value);
-        }
-        return undef;    
-    }
-}
-
-sub check {
-    my $self  = shift;
-    my $value = shift;
-    $self->_compiled_type_constraint->($value);
-}
-
 sub validate {
-    my $self  = shift;
-    my $value = shift;
+    my ($self, $value) = @_;
     my $message;
     foreach my $type (@{$self->type_constraints}) {
         my $err = $type->validate($value);
@@ -119,20 +68,6 @@ sub is_subtype_of {
     return 0;
 }
 
-## hand optimized constraints 
-
-# NOTE:
-# it will just use all the hand optimized 
-# type constraints from it's list of type 
-# constraints automatically, but there is 
-# no simple way to optimize it even more 
-# (without B::Deparse or something). So  
-# we just stop here.
-# - SL
-
-sub has_hand_optimized_type_constraint { 0 }
-sub hand_optimized_type_constraint     { undef }
-
 1;
 
 __END__
index cc36e8e..9420e55 100644 (file)
@@ -33,7 +33,10 @@ sub optimize_as                  (&);
 sub enum                         ($;@);
 
 use Moose::Meta::TypeConstraint;
+use Moose::Meta::TypeConstraint::Union;
+use Moose::Meta::TypeConstraint::Container;
 use Moose::Meta::TypeCoercion;
+use Moose::Meta::TypeCoercion::Union;
 use Moose::Meta::TypeConstraint::Registry;
 
 my @exports = qw/
@@ -130,11 +133,15 @@ sub _install_type_coercions ($$) {
 
 sub create_type_constraint_union (@) {
     my (@type_constraint_names) = @_;
-    return Moose::Meta::TypeConstraint->union(
-        map { 
-            $REGISTRY->get_type_constraint($_) 
-        } @type_constraint_names
-    );
+    (scalar @type_constraint_names >= 2)
+        || confess "You must pass in at least 2 type names to make a union";    
+    return Moose::Meta::TypeConstraint::Union->new(
+        type_constraints => [
+            map { 
+                $REGISTRY->get_type_constraint($_) 
+            } @type_constraint_names        
+        ],
+    );    
 }
 
 sub export_type_constraints_as_functions {
index 556d3e7..49111df 100644 (file)
@@ -21,7 +21,7 @@ ok($Str->check('String'), '... Str can accept an String value');
 ok(!$Undef->check('String'), '... Undef cannot accept an Str value');
 ok($Undef->check(undef), '... Undef can accept an Undef value');
 
-my $Str_or_Undef = Moose::Meta::TypeConstraint->union($Str, $Undef);
+my $Str_or_Undef = Moose::Meta::TypeConstraint::Union->new(type_constraints => [$Str, $Undef]);
 isa_ok($Str_or_Undef, 'Moose::Meta::TypeConstraint::Union');
 
 ok($Str_or_Undef->check(undef), '... (Str | Undef) can accept an Undef value');
@@ -40,7 +40,7 @@ ok(!$ArrayRef->check({}), '... ArrayRef cannot accept an {} value');
 ok($HashRef->check({}), '... HashRef can accept an {} value');
 ok(!$HashRef->check([]), '... HashRef cannot accept an [] value');
 
-my $HashOrArray = Moose::Meta::TypeConstraint->union($ArrayRef, $HashRef);
+my $HashOrArray = Moose::Meta::TypeConstraint::Union->new(type_constraints => [$ArrayRef, $HashRef]);
 isa_ok($HashOrArray, 'Moose::Meta::TypeConstraint::Union');
 
 ok($HashOrArray->check([]), '... (ArrayRef | HashRef) can accept []');