trim excess whitespace
[gitmo/Moose.git] / lib / Moose / Util / TypeConstraints.pm
index 50d74ed..063439d 100644 (file)
@@ -18,12 +18,14 @@ sub where (&);
 sub via (&);
 sub message (&);
 sub optimize_as (&);
+sub inline_as (&);
 
 ## --------------------------------------------------------
 
 use Moose::Deprecated;
 use Moose::Meta::TypeConstraint;
 use Moose::Meta::TypeConstraint::Union;
+use Moose::Meta::TypeConstraint::Intersection;
 use Moose::Meta::TypeConstraint::Parameterized;
 use Moose::Meta::TypeConstraint::Parameterizable;
 use Moose::Meta::TypeConstraint::Class;
@@ -32,15 +34,16 @@ use Moose::Meta::TypeConstraint::Enum;
 use Moose::Meta::TypeConstraint::DuckType;
 use Moose::Meta::TypeCoercion;
 use Moose::Meta::TypeCoercion::Union;
+use Moose::Meta::TypeCoercion::Intersection;
 use Moose::Meta::TypeConstraint::Registry;
 
 Moose::Exporter->setup_import_methods(
     as_is => [
         qw(
             type subtype class_type role_type maybe_type duck_type
-            as where message optimize_as
+            as where message optimize_as inline_as
             coerce from via
-            enum
+            enum union
             find_type_constraint
             register_type_constraint
             match_on_type )
@@ -68,13 +71,26 @@ sub export_type_constraints_as_functions {
 }
 
 sub create_type_constraint_union {
+    _create_type_constraint_union(\@_);
+}
+
+sub create_named_type_constraint_union {
+    my $name = shift;
+    _create_type_constraint_union($name, \@_);
+}
+
+sub _create_type_constraint_union {
+    my $name;
+    $name = shift if @_ > 1;
+    my @tcs = @{ shift() };
+
     my @type_constraint_names;
 
-    if ( scalar @_ == 1 && _detect_type_constraint_union( $_[0] ) ) {
-        @type_constraint_names = _parse_type_constraint_union( $_[0] );
+    if ( scalar @tcs == 1 && _detect_type_constraint_union( $tcs[0] ) ) {
+        @type_constraint_names = _parse_type_constraint_union( $tcs[0] );
     }
     else {
-        @type_constraint_names = @_;
+        @type_constraint_names = @tcs;
     }
 
     ( scalar @type_constraint_names >= 2 )
@@ -87,8 +103,35 @@ sub create_type_constraint_union {
             "Could not locate type constraint ($_) for the union");
     } @type_constraint_names;
 
-    return Moose::Meta::TypeConstraint::Union->new(
-        type_constraints => \@type_constraints );
+    my %options = (
+      type_constraints => \@type_constraints
+    );
+    $options{name} = $name if defined $name;
+
+    return Moose::Meta::TypeConstraint::Union->new(%options);
+}
+
+sub create_type_constraint_intersection {
+    my @type_constraint_names;
+
+    if (scalar @_ == 1 && _detect_type_constraint_intersection($_[0])) {
+        @type_constraint_names = _parse_type_constraint_intersection($_[0]);
+    }
+    else {
+        @type_constraint_names = @_;
+    }
+
+    (scalar @type_constraint_names >= 2)
+        || __PACKAGE__->_throw_error("You must pass in at least 2 type names to make an intersection");
+
+    my @type_constraints = map {
+        find_or_parse_type_constraint($_) ||
+         __PACKAGE__->_throw_error("Could not locate type constraint ($_) for the intersection");
+    } @type_constraint_names;
+
+    return Moose::Meta::TypeConstraint::Intersection->new(
+        type_constraints => \@type_constraints
+    );
 }
 
 sub create_parameterized_type_constraint {
@@ -213,7 +256,10 @@ sub find_or_parse_type_constraint {
     if ( $constraint = find_type_constraint($type_constraint_name) ) {
         return $constraint;
     }
-    elsif ( _detect_type_constraint_union($type_constraint_name) ) {
+    elsif (_detect_type_constraint_intersection($type_constraint_name)) {
+        $constraint = create_type_constraint_intersection($type_constraint_name);
+    }
+    elsif (_detect_type_constraint_union($type_constraint_name)) {
         $constraint = create_type_constraint_union($type_constraint_name);
     }
     elsif ( _detect_parameterized_type_constraint($type_constraint_name) ) {
@@ -268,67 +314,17 @@ sub register_type_constraint {
 # type constructors
 
 sub type {
-
-    # back-compat version, called without sugar
-    if ( !any { ( reftype($_) || '' ) eq 'HASH' } @_ ) {
-        Moose::Deprecated::deprecated(
-            feature => 'type without sugar',
-            message =>
-                'Calling type() with a simple list of parameters is deprecated. This will be an error in Moose 2.0200.'
-        );
-
-        return _create_type_constraint( $_[0], undef, $_[1] );
-    }
-
     my $name = shift;
 
     my %p = map { %{$_} } @_;
 
     return _create_type_constraint(
         $name, undef, $p{where}, $p{message},
-        $p{optimize_as}
+        $p{optimize_as}, $p{inline_as},
     );
 }
 
 sub subtype {
-
-    # crazy back-compat code for being called without sugar ...
-    #
-    # subtype 'Parent', sub { where };
-    if ( scalar @_ == 2 && ( reftype( $_[1] ) || '' ) eq 'CODE' ) {
-        Moose::Deprecated::deprecated(
-            feature => 'subtype without sugar',
-            message =>
-                'Calling subtype() with a simple list of parameters is deprecated. This will be an error in Moose 2.0200.'
-        );
-
-        return _create_type_constraint( undef, @_ );
-    }
-
-    # subtype 'Parent', sub { where }, sub { message };
-    # subtype 'Parent', sub { where }, sub { message }, sub { optimized };
-    if ( scalar @_ >= 3 && all { ( reftype($_) || '' ) eq 'CODE' }
-        @_[ 1 .. $#_ ] ) {
-        Moose::Deprecated::deprecated(
-            feature => 'subtype without sugar',
-            message =>
-                'Calling subtype() with a simple list of parameters is deprecated. This will be an error in Moose 2.0200.'
-        );
-
-        return _create_type_constraint( undef, @_ );
-    }
-
-    # subtype 'Name', 'Parent', ...
-    if ( scalar @_ >= 2 && all { !ref } @_[ 0, 1 ] ) {
-        Moose::Deprecated::deprecated(
-            feature => 'subtype without sugar',
-            message =>
-                'Calling subtype() with a simple list of parameters is deprecated. This will be an error in Moose 2.0200.'
-        );
-
-        return _create_type_constraint(@_);
-    }
-
     if ( @_ == 1 && !ref $_[0] ) {
         __PACKAGE__->_throw_error(
             'A subtype cannot consist solely of a name, it must have a parent'
@@ -349,7 +345,7 @@ sub subtype {
 
     return _create_type_constraint(
         $name, $p{as}, $p{where}, $p{message},
-        $p{optimize_as}
+        $p{optimize_as}, $p{inline_as},
     );
 }
 
@@ -419,6 +415,7 @@ sub as { { as => shift }, @_ }
 sub where (&)       { { where       => $_[0] } }
 sub message (&)     { { message     => $_[0] } }
 sub optimize_as (&) { { optimize_as => $_[0] } }
+sub inline_as (&)   { { inline_as   => $_[0] } }
 
 sub from    {@_}
 sub via (&) { $_[0] }
@@ -449,6 +446,25 @@ sub enum {
     );
 }
 
+sub union {
+  my ( $type_name, @constraints ) = @_;
+  if ( ref $type_name eq 'ARRAY' ) {
+    @constraints == 0
+      || __PACKAGE__->_throw_error("union called with an array reference and additional arguments.");
+    @constraints = @$type_name;
+    $type_name   = undef;
+  }
+  if ( @constraints == 1 && ref $constraints[0] eq 'ARRAY' ) {
+    @constraints = @{ $constraints[0] };
+  }
+  if ( defined $type_name ) {
+    return register_type_constraint(
+      create_named_type_constraint_union( $type_name, @constraints )
+    );
+  }
+  return create_type_constraint_union( @constraints );
+}
+
 sub create_enum_type_constraint {
     my ( $type_name, $values ) = @_;
 
@@ -510,6 +526,7 @@ sub _create_type_constraint ($$$;$$) {
     my $check     = shift;
     my $message   = shift;
     my $optimized = shift;
+    my $inlined   = shift;
 
     my $pkg_defined_in = scalar( caller(1) );
 
@@ -536,6 +553,7 @@ sub _create_type_constraint ($$$;$$) {
         ( $check     ? ( constraint => $check )     : () ),
         ( $message   ? ( message    => $message )   : () ),
         ( $optimized ? ( optimized  => $optimized ) : () ),
+        ( $inlined   ? ( inlined    => $inlined )   : () ),
     );
 
     my $constraint;
@@ -594,8 +612,9 @@ sub _install_type_coercions ($$) {
     my $type_atom   = qr{ (?>$valid_chars+) }x;
     my $ws          = qr{ (?>\s*) }x;
     my $op_union    = qr{ $ws \| $ws }x;
+    my $op_intersection = qr{ $ws & $ws }x;
 
-    my ($type, $type_capture_parts, $type_with_parameter, $union, $any);
+    my ($type, $type_capture_parts, $type_with_parameter, $union, $any, $intersection);
     if (Class::MOP::IS_RUNNING_ON_5_10) {
         my $type_pattern
             = q{  (?&type_atom)  (?: \[ (?&ws)  (?&any)  (?&ws) \] )? };
@@ -605,18 +624,22 @@ sub _install_type_coercions ($$) {
             = q{  (?&type_atom)      \[ (?&ws)  (?&any)  (?&ws) \]    };
         my $union_pattern
             = q{ (?&type) (?> (?: (?&op_union) (?&type) )+ ) };
+        my $intersection_pattern
+            = q{ (?&type) (?> (?: (?&op_intersection) (?&type) )+ ) };
         my $any_pattern
-            = q{ (?&type) | (?&union) };
+            = q{ (?&type) | (?&union) | (?&intersection) };
 
         my $defines = qr{(?(DEFINE)
             (?<valid_chars>         $valid_chars)
             (?<type_atom>           $type_atom)
             (?<ws>                  $ws)
             (?<op_union>            $op_union)
+            (?<op_intersection>     $op_intersection)
             (?<type>                $type_pattern)
             (?<type_capture_parts>  $type_capture_parts_pattern)
             (?<type_with_parameter> $type_with_parameter_pattern)
             (?<union>               $union_pattern)
+            (?<intersection>        $intersection_pattern)
             (?<any>                 $any_pattern)
         )}x;
 
@@ -624,6 +647,7 @@ sub _install_type_coercions ($$) {
         $type_capture_parts  = qr{ $type_capture_parts_pattern  $defines }x;
         $type_with_parameter = qr{ $type_with_parameter_pattern $defines }x;
         $union               = qr{ $union_pattern               $defines }x;
+        $intersection        = qr{ $intersection_pattern        $defines }x;
         $any                 = qr{ $any_pattern                 $defines }x;
     }
     else {
@@ -635,8 +659,10 @@ sub _install_type_coercions ($$) {
             = qr{  $type_atom      \[ $ws  (??{$any})  $ws \]    }x;
         $union
             = qr{ $type (?> (?: $op_union $type )+ ) }x;
+        $intersection
+            = qr{ $type (?> (?: $op_intersection $type )+ ) }x;
         $any
-            = qr{ $type | $union }x;
+            = qr{ $type | $union | $intersection }x;
     }
 
 
@@ -667,10 +693,31 @@ sub _install_type_coercions ($$) {
         @rv;
     }
 
+    sub _parse_type_constraint_intersection {
+        { no warnings 'void'; $any; } # force capture of interpolated lexical
+        my $given = shift;
+        my @rv;
+        while ( $given =~ m{ \G (?: $op_intersection )? ($type) }gcx ) {
+            push @rv => $1;
+        }
+        (pos($given) eq length($given))
+            || __PACKAGE__->_throw_error("'$given' didn't parse (parse-pos="
+                     . pos($given)
+                     . " and str-length="
+                     . length($given)
+                     . ")");
+        @rv;
+    }
+
     sub _detect_type_constraint_union {
         { no warnings 'void'; $any; }  # force capture of interpolated lexical
         $_[0] =~ m{^ $type $op_union $type ( $op_union .* )? $}x;
     }
+
+    sub _detect_type_constraint_intersection {
+        { no warnings 'void'; $any; } # force capture of interpolated lexical
+        $_[0] =~ m{^ $type $op_intersection $type ( $op_intersection .* )? $}x;
+    }
 }
 
 ## --------------------------------------------------------
@@ -691,6 +738,7 @@ $_->make_immutable(
     qw(
     Moose::Meta::TypeConstraint
     Moose::Meta::TypeConstraint::Union
+    Moose::Meta::TypeConstraint::Intersection
     Moose::Meta::TypeConstraint::Parameterized
     Moose::Meta::TypeConstraint::Parameterizable
     Moose::Meta::TypeConstraint::Class
@@ -761,6 +809,8 @@ __END__
 
   enum 'RGBColors', [qw(red green blue)];
 
+  union 'StringOrArray', [qw( String Array )];
+
   no Moose::Util::TypeConstraints;
 
 =head1 DESCRIPTION
@@ -827,7 +877,7 @@ that hierarchy represented visually.
               CodeRef
               RegexpRef
               GlobRef
-                  FileHandle
+              FileHandle
               Object
 
 B<NOTE:> Any type followed by a type parameter C<[`a]> can be
@@ -1017,6 +1067,33 @@ can then be used in an attribute definition like so:
       isa => enum([qw[ ascending descending ]]),
   );
 
+=item B<union ($name, \@constraints)>
+
+This will create a basic subtype where any of the provided constraints
+may match in order to satisfy this constraint.
+
+=item B<union (\@constraints)>
+
+If passed an ARRAY reference as the only parameter instead of the
+C<$name>, C<\@constraints> pair, this will create an unnamed union.
+This can then be used in an attribute definition like so:
+
+  has 'items' => (
+      is => 'ro',
+      isa => union([qw[ Str ArrayRef ]]),
+  );
+
+This is similar to the existing string union:
+
+  isa => 'Str|ArrayRef'
+
+except that it supports anonymous elements as child constraints:
+
+  has 'color' => (
+    isa => 'ro',
+    isa => union([ 'Int',  enum([qw[ red green blue ]]) ]),
+  );
+
 =item B<as 'Parent'>
 
 This is just sugar for the type constraint construction syntax.
@@ -1041,8 +1118,38 @@ constraint fails, then the code block is run with the value provided
 in C<$_>. This reference should return a string, which will be used in
 the text of the exception thrown.
 
+=item B<inline_as { ... }>
+
+This can be used to define a "hand optimized" inlinable version of your type
+constraint.
+
+You provide a subroutine which will be called I<as a method> on a
+L<Moose::Meta::TypeConstraint> object. It will receive a single parameter, the
+name of the variable to check, typically something like C<"$_"> or C<"$_[0]">.
+
+The subroutine should return a code string suitable for inlining. You can
+assume that the check will be wrapped in parentheses when it is inlined.
+
+The inlined code should include any checks that your type's parent types
+do. For example, the C<Value> type's inlining sub looks like this:
+
+    sub {
+        'defined(' . $_[1] . ')'
+        . ' && !ref(' . $_[1] . ')'
+    }
+
+Note that it checks if the variable is defined, since it is a subtype of
+the C<Defined> type.  However, to avoid repeating code, this can be optimized as:
+
+    sub {
+        $_[0]->parent()->_inline_check($_[1])
+        . ' && !ref(' . $_[1] . ')'
+    }
+
 =item B<optimize_as { ... }>
 
+B<This feature is deprecated, use C<inline_as> instead.>
+
 This can be used to define a "hand optimized" version of your
 type constraint which can be used to avoid traversing a subtype
 constraint hierarchy.
@@ -1061,7 +1168,7 @@ parameters:
 
   type( 'Foo', { where => ..., message => ... } );
 
-The valid hashref keys are C<where>, C<message>, and C<optimize_as>.
+The valid hashref keys are C<where>, C<message>, and C<inlined_as>.
 
 =back
 
@@ -1207,10 +1314,17 @@ form. This removes any whitespace in the string.
 
 =item B<create_type_constraint_union($pipe_separated_types | @type_constraint_names)>
 
+=item B<create_named_type_constraint_union($name, $pipe_separated_types | @type_constraint_names)>
+
 This can take a union type specification like C<'Int|ArrayRef[Int]'>,
 or a list of names. It returns a new
 L<Moose::Meta::TypeConstraint::Union> object.
 
+=item B<create_type_constraint_intersection ($pipe_separated_types | @type_constraint_names)>
+
+Given string with C<$pipe_separated_types> or a list of C<@type_constraint_names>,
+this will return a L<Moose::Meta::TypeConstraint::Intersection> instance.
+
 =item B<create_parameterized_type_constraint($type_name)>
 
 Given a C<$type_name> in the form of C<'BaseType[ContainerType]'>,