diff options
Diffstat (limited to 'lib/Moose/Util')
-rw-r--r-- | lib/Moose/Util/MetaRole.pm | 329 | ||||
-rw-r--r-- | lib/Moose/Util/TypeConstraints.pm | 1459 | ||||
-rw-r--r-- | lib/Moose/Util/TypeConstraints/Builtins.pm | 305 |
3 files changed, 2093 insertions, 0 deletions
diff --git a/lib/Moose/Util/MetaRole.pm b/lib/Moose/Util/MetaRole.pm new file mode 100644 index 0000000..c85bc3c --- /dev/null +++ b/lib/Moose/Util/MetaRole.pm @@ -0,0 +1,329 @@ +package Moose::Util::MetaRole; +our $VERSION = '2.1405'; + +use strict; +use warnings; +use Scalar::Util 'blessed'; + +use List::Util 1.33 qw( first all ); +use Moose::Deprecated; +use Moose::Util 'throw_exception'; + +sub apply_metaroles { + my %args = @_; + + my $for = _metathing_for( $args{for} ); + + if ( $for->isa('Moose::Meta::Role') ) { + return _make_new_metaclass( $for, $args{role_metaroles}, 'role' ); + } + else { + return _make_new_metaclass( $for, $args{class_metaroles}, 'class' ); + } +} + +sub _metathing_for { + my $passed = shift; + + my $found + = blessed $passed + ? $passed + : Class::MOP::class_of($passed); + + return $found + if defined $found + && blessed $found + && ( $found->isa('Moose::Meta::Role') + || $found->isa('Moose::Meta::Class') ); + + local $Carp::CarpLevel = $Carp::CarpLevel + 1; + + throw_exception( InvalidArgPassedToMooseUtilMetaRole => argument => $passed ); +} + +sub _make_new_metaclass { + my $for = shift; + my $roles = shift; + my $primary = shift; + + return $for unless keys %{$roles}; + + my $new_metaclass + = exists $roles->{$primary} + ? _make_new_class( ref $for, $roles->{$primary} ) + : blessed $for; + + my %classes; + + for my $key ( grep { $_ ne $primary } keys %{$roles} ) { + my $attr = first {$_} + map { $for->meta->find_attribute_by_name($_) } ( + $key . '_metaclass', + $key . '_class' + ); + + my $reader = $attr->get_read_method; + + $classes{ $attr->init_arg } + = _make_new_class( $for->$reader(), $roles->{$key} ); + } + + my $new_meta = $new_metaclass->reinitialize( $for, %classes ); + + return $new_meta; +} + +sub apply_base_class_roles { + my %args = @_; + + my $meta = _metathing_for( $args{for} || $args{for_class} ); + throw_exception( CannotApplyBaseClassRolesToRole => params => \%args, + role_name => $meta->name, + ) + if $meta->isa('Moose::Meta::Role'); + + my $new_base = _make_new_class( + $meta->name, + $args{roles}, + [ $meta->superclasses() ], + ); + + $meta->superclasses($new_base) + if $new_base ne $meta->name(); +} + +sub _make_new_class { + my $existing_class = shift; + my $roles = shift; + my $superclasses = shift || [$existing_class]; + + return $existing_class unless $roles; + + my $meta = Class::MOP::Class->initialize($existing_class); + + return $existing_class + if $meta->can('does_role') && all { $meta->does_role($_) } + grep { !ref $_ } @{$roles}; + + return Moose::Meta::Class->create_anon_class( + superclasses => $superclasses, + roles => $roles, + cache => 1, + )->name(); +} + +1; + +# ABSTRACT: Apply roles to any metaclass, as well as the object base class + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Moose::Util::MetaRole - Apply roles to any metaclass, as well as the object base class + +=head1 VERSION + +version 2.1405 + +=head1 SYNOPSIS + + package MyApp::Moose; + + use Moose (); + use Moose::Exporter; + use Moose::Util::MetaRole; + + use MyApp::Role::Meta::Class; + use MyApp::Role::Meta::Method::Constructor; + use MyApp::Role::Object; + + Moose::Exporter->setup_import_methods( also => 'Moose' ); + + sub init_meta { + shift; + my %args = @_; + + Moose->init_meta(%args); + + Moose::Util::MetaRole::apply_metaroles( + for => $args{for_class}, + class_metaroles => { + class => ['MyApp::Role::Meta::Class'], + constructor => ['MyApp::Role::Meta::Method::Constructor'], + }, + ); + + Moose::Util::MetaRole::apply_base_class_roles( + for => $args{for_class}, + roles => ['MyApp::Role::Object'], + ); + + return $args{for_class}->meta(); + } + +=head1 DESCRIPTION + +This utility module is designed to help authors of Moose extensions +write extensions that are able to cooperate with other Moose +extensions. To do this, you must write your extensions as roles, which +can then be dynamically applied to the caller's metaclasses. + +This module makes sure to preserve any existing superclasses and roles +already set for the meta objects, which means that any number of +extensions can apply roles in any order. + +=head1 USAGE + +The easiest way to use this module is through L<Moose::Exporter>, which can +generate the appropriate C<init_meta> method for you, and make sure it is +called when imported. + +=head1 FUNCTIONS + +This module provides two functions. + +=head2 apply_metaroles( ... ) + +This function will apply roles to one or more metaclasses for the specified +class. It will return a new metaclass object for the class or role passed in +the "for" parameter. + +It accepts the following parameters: + +=over 4 + +=item * for => $name + +This specifies the class or for which to alter the meta classes. This can be a +package name, or an appropriate meta-object (a L<Moose::Meta::Class> or +L<Moose::Meta::Role>). + +=item * class_metaroles => \%roles + +This is a hash reference specifying which metaroles will be applied to the +class metaclass and its contained metaclasses and helper classes. + +Each key should in turn point to an array reference of role names. + +It accepts the following keys: + +=over 8 + +=item class + +=item attribute + +=item method + +=item wrapped_method + +=item instance + +=item constructor + +=item destructor + +=item error + +=back + +=item * role_metaroles => \%roles + +This is a hash reference specifying which metaroles will be applied to the +role metaclass and its contained metaclasses and helper classes. + +It accepts the following keys: + +=over 8 + +=item role + +=item attribute + +=item method + +=item required_method + +=item conflicting_method + +=item application_to_class + +=item application_to_role + +=item application_to_instance + +=item application_role_summation + +=item applied_attribute + +=back + +=back + +=head2 apply_base_class_roles( for => $class, roles => \@roles ) + +This function will apply the specified roles to the object's base class. + +=head1 BUGS + +See L<Moose/BUGS> for details on reporting bugs. + +=head1 AUTHORS + +=over 4 + +=item * + +Stevan Little <stevan.little@iinteractive.com> + +=item * + +Dave Rolsky <autarch@urth.org> + +=item * + +Jesse Luehrs <doy@tozt.net> + +=item * + +Shawn M Moore <code@sartak.org> + +=item * + +יובל קוג'מן (Yuval Kogman) <nothingmuch@woobling.org> + +=item * + +Karen Etheridge <ether@cpan.org> + +=item * + +Florian Ragwitz <rafl@debian.org> + +=item * + +Hans Dieter Pearcey <hdp@weftsoar.net> + +=item * + +Chris Prather <chris@prather.org> + +=item * + +Matt S Trout <mst@shadowcat.co.uk> + +=back + +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2006 by Infinity Interactive, Inc.. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut diff --git a/lib/Moose/Util/TypeConstraints.pm b/lib/Moose/Util/TypeConstraints.pm new file mode 100644 index 0000000..e4b75e3 --- /dev/null +++ b/lib/Moose/Util/TypeConstraints.pm @@ -0,0 +1,1459 @@ +package Moose::Util::TypeConstraints; +our $VERSION = '2.1405'; + +use Carp (); +use Scalar::Util qw( blessed ); +use Moose::Exporter; +use Moose::Deprecated; + +## -------------------------------------------------------- +# Prototyped subs must be predeclared because we have a +# circular dependency with Moose::Meta::Attribute et. al. +# so in case of us being use'd first the predeclaration +# ensures the prototypes are in scope when consumers are +# compiled. + +# dah sugah! +sub where (&); +sub via (&); +sub message (&); +sub inline_as (&); + +## -------------------------------------------------------- + +use Moose::Meta::TypeConstraint; +use Moose::Meta::TypeConstraint::Union; +use Moose::Meta::TypeConstraint::Parameterized; +use Moose::Meta::TypeConstraint::Parameterizable; +use Moose::Meta::TypeConstraint::Class; +use Moose::Meta::TypeConstraint::Role; +use Moose::Meta::TypeConstraint::Enum; +use Moose::Meta::TypeConstraint::DuckType; +use Moose::Meta::TypeCoercion; +use Moose::Meta::TypeCoercion::Union; +use Moose::Meta::TypeConstraint::Registry; + +use Moose::Util 'throw_exception'; + +Moose::Exporter->setup_import_methods( + as_is => [ + qw( + type subtype class_type role_type maybe_type duck_type + as where message inline_as + coerce from via + enum union + find_type_constraint + register_type_constraint + match_on_type ) + ], +); + +## -------------------------------------------------------- +## type registry and some useful functions for it +## -------------------------------------------------------- + +my $REGISTRY = Moose::Meta::TypeConstraint::Registry->new; + +sub get_type_constraint_registry {$REGISTRY} +sub list_all_type_constraints { keys %{ $REGISTRY->type_constraints } } + +sub export_type_constraints_as_functions { + my $pkg = caller(); + no strict 'refs'; + foreach my $constraint ( keys %{ $REGISTRY->type_constraints } ) { + my $tc = $REGISTRY->get_type_constraint($constraint) + ->_compiled_type_constraint; + *{"${pkg}::${constraint}"} + = sub { $tc->( $_[0] ) ? 1 : undef }; # the undef is for compat + } +} + +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 @tcs == 1 && _detect_type_constraint_union( $tcs[0] ) ) { + @type_constraint_names = _parse_type_constraint_union( $tcs[0] ); + } + else { + @type_constraint_names = @tcs; + } + + ( scalar @type_constraint_names >= 2 ) + || throw_exception("UnionTakesAtleastTwoTypeNames"); + + my @type_constraints = map { + find_or_parse_type_constraint($_) + || throw_exception( CouldNotLocateTypeConstraintForUnion => type_name => $_ ); + } @type_constraint_names; + + my %options = ( + type_constraints => \@type_constraints + ); + $options{name} = $name if defined $name; + + return Moose::Meta::TypeConstraint::Union->new(%options); +} + + +sub create_parameterized_type_constraint { + my $type_constraint_name = shift; + my ( $base_type, $type_parameter ) + = _parse_parameterized_type_constraint($type_constraint_name); + + ( defined $base_type && defined $type_parameter ) + || throw_exception( InvalidTypeGivenToCreateParameterizedTypeConstraint => type_name => $type_constraint_name ); + + if ( $REGISTRY->has_type_constraint($base_type) ) { + my $base_type_tc = $REGISTRY->get_type_constraint($base_type); + return _create_parameterized_type_constraint( + $base_type_tc, + $type_parameter + ); + } + else { + throw_exception( InvalidBaseTypeGivenToCreateParameterizedTypeConstraint => type_name => $base_type ); + } +} + +sub _create_parameterized_type_constraint { + my ( $base_type_tc, $type_parameter ) = @_; + if ( $base_type_tc->can('parameterize') ) { + return $base_type_tc->parameterize($type_parameter); + } + else { + return Moose::Meta::TypeConstraint::Parameterized->new( + name => $base_type_tc->name . '[' . $type_parameter . ']', + parent => $base_type_tc, + type_parameter => + find_or_create_isa_type_constraint($type_parameter), + ); + } +} + +#should we also support optimized checks? +sub create_class_type_constraint { + my ( $class, $options ) = @_; + +# too early for this check +#find_type_constraint("ClassName")->check($class) +# || __PACKAGE__->_throw_error("Can't create a class type constraint because '$class' is not a class name"); + + my $pkg_defined_in = $options->{package_defined_in} || scalar( caller(1) ); + + if (my $type = $REGISTRY->get_type_constraint($class)) { + if (!($type->isa('Moose::Meta::TypeConstraint::Class') && $type->class eq $class)) { + throw_exception( TypeConstraintIsAlreadyCreated => package_defined_in => $pkg_defined_in, + type_name => $type->name, + ); + } + else { + return $type; + } + } + + my %options = ( + class => $class, + name => $class, + package_defined_in => $pkg_defined_in, + %{ $options || {} }, # overrides options from above + ); + + $options{name} ||= "__ANON__"; + + my $tc = Moose::Meta::TypeConstraint::Class->new(%options); + $REGISTRY->add_type_constraint($tc); + return $tc; +} + +sub create_role_type_constraint { + my ( $role, $options ) = @_; + +# too early for this check +#find_type_constraint("ClassName")->check($class) +# || __PACKAGE__->_throw_error("Can't create a class type constraint because '$class' is not a class name"); + + my $pkg_defined_in = $options->{package_defined_in} || scalar( caller(1) ); + + if (my $type = $REGISTRY->get_type_constraint($role)) { + if (!($type->isa('Moose::Meta::TypeConstraint::Role') && $type->role eq $role)) { + throw_exception( TypeConstraintIsAlreadyCreated => type_name => $type->name, + package_defined_in => $pkg_defined_in + ); + } + else { + return $type; + } + } + + my %options = ( + role => $role, + name => $role, + package_defined_in => $pkg_defined_in, + %{ $options || {} }, + ); + + $options{name} ||= "__ANON__"; + + my $tc = Moose::Meta::TypeConstraint::Role->new(%options); + $REGISTRY->add_type_constraint($tc); + return $tc; +} + +sub find_or_create_type_constraint { + my ( $type_constraint_name, $options_for_anon_type ) = @_; + + if ( my $constraint + = find_or_parse_type_constraint($type_constraint_name) ) { + return $constraint; + } + elsif ( defined $options_for_anon_type ) { + + # NOTE: + # if there is no $options_for_anon_type + # specified, then we assume they don't + # want to create one, and return nothing. + + # otherwise assume that we should create + # an ANON type with the $options_for_anon_type + # options which can be passed in. It should + # be noted that these don't get registered + # so we need to return it. + # - SL + return Moose::Meta::TypeConstraint->new( + name => '__ANON__', + %{$options_for_anon_type} + ); + } + + return; +} + +sub find_or_create_isa_type_constraint { + my ($type_constraint_name, $options) = @_; + find_or_parse_type_constraint($type_constraint_name) + || create_class_type_constraint($type_constraint_name, $options); +} + +sub find_or_create_does_type_constraint { + my ($type_constraint_name, $options) = @_; + find_or_parse_type_constraint($type_constraint_name) + || create_role_type_constraint($type_constraint_name, $options); +} + +sub find_or_parse_type_constraint { + my $type_constraint_name = normalize_type_constraint_name(shift); + my $constraint; + + if ( $constraint = find_type_constraint($type_constraint_name) ) { + return $constraint; + } + elsif ( _detect_type_constraint_union($type_constraint_name) ) { + $constraint = create_type_constraint_union($type_constraint_name); + } + elsif ( _detect_parameterized_type_constraint($type_constraint_name) ) { + $constraint + = create_parameterized_type_constraint($type_constraint_name); + } + else { + return; + } + + $REGISTRY->add_type_constraint($constraint); + return $constraint; +} + +sub normalize_type_constraint_name { + my $type_constraint_name = shift; + $type_constraint_name =~ s/\s//g; + return $type_constraint_name; +} + +sub _confess { + my $error = shift; + + local $Carp::CarpLevel = $Carp::CarpLevel + 1; + Carp::confess($error); +} + +## -------------------------------------------------------- +## exported functions ... +## -------------------------------------------------------- + +sub find_type_constraint { + my $type = shift; + + if ( blessed $type and $type->isa("Moose::Meta::TypeConstraint") ) { + return $type; + } + else { + return unless $REGISTRY->has_type_constraint($type); + return $REGISTRY->get_type_constraint($type); + } +} + +sub register_type_constraint { + my $constraint = shift; + throw_exception( CannotRegisterUnnamedTypeConstraint => type => $constraint ) + unless defined $constraint->name; + $REGISTRY->add_type_constraint($constraint); + return $constraint; +} + +# type constructors + +sub type { + my $name = shift; + + my %p = map { %{$_} } @_; + + return _create_type_constraint( + $name, undef, $p{where}, $p{message}, + $p{inline_as}, + ); +} + +sub subtype { + if ( @_ == 1 && !ref $_[0] ) { + throw_exception( NoParentGivenToSubtype => name => $_[0] ); + } + + # The blessed check is mostly to accommodate MooseX::Types, which + # uses an object which overloads stringification as a type name. + my $name = ref $_[0] && !blessed $_[0] ? undef : shift; + + my %p = map { %{$_} } @_; + + # subtype Str => where { ... }; + if ( !exists $p{as} ) { + $p{as} = $name; + $name = undef; + } + + return _create_type_constraint( + $name, $p{as}, $p{where}, $p{message}, + $p{inline_as}, + ); +} + +sub class_type { + create_class_type_constraint(@_); +} + +sub role_type ($;$) { + create_role_type_constraint(@_); +} + +sub maybe_type { + my ($type_parameter) = @_; + + register_type_constraint( + $REGISTRY->get_type_constraint('Maybe')->parameterize($type_parameter) + ); +} + +sub duck_type { + my ( $type_name, @methods ) = @_; + if ( ref $type_name eq 'ARRAY' && !@methods ) { + @methods = ($type_name); + $type_name = undef; + } + if ( @methods == 1 && ref $methods[0] eq 'ARRAY' ) { + @methods = @{ $methods[0] }; + } + else { + Moose::Deprecated::deprecated( + feature => 'non-arrayref form of duck_type', + message => "Passing a list of values to duck_type is deprecated. " + . "The method names should be wrapped in an arrayref.", + ); + } + + register_type_constraint( + create_duck_type_constraint( + $type_name, + \@methods, + ) + ); +} + +sub coerce { + my ( $type_name, @coercion_map ) = @_; + _install_type_coercions( $type_name, \@coercion_map ); +} + +# The trick of returning @_ lets us avoid having to specify a +# prototype. Perl will parse this: +# +# subtype 'Foo' +# => as 'Str' +# => where { ... } +# +# as this: +# +# subtype( 'Foo', as( 'Str', where { ... } ) ); +# +# If as() returns all its extra arguments, this just works, and +# preserves backwards compatibility. +sub as { { as => shift }, @_ } +sub where (&) { { where => $_[0] } } +sub message (&) { { message => $_[0] } } +sub inline_as (&) { { inline_as => $_[0] } } + +sub from {@_} +sub via (&) { $_[0] } + +sub enum { + my ( $type_name, @values ) = @_; + + # NOTE: + # if only an array-ref is passed then + # you get an anon-enum + # - SL + if ( ref $type_name eq 'ARRAY' ) { + @values == 0 + || throw_exception( EnumCalledWithAnArrayRefAndAdditionalArgs => array => $type_name, + args => \@values + ); + @values = ($type_name); + $type_name = undef; + } + if ( @values == 1 && ref $values[0] eq 'ARRAY' ) { + @values = @{ $values[0] }; + } + else { + Moose::Deprecated::deprecated( + feature => 'non-arrayref form of enum', + message => "Passing a list of values to enum is deprecated. " + . "Enum values should be wrapped in an arrayref.", + ); + } + + register_type_constraint( + create_enum_type_constraint( + $type_name, + \@values, + ) + ); +} + +sub union { + my ( $type_name, @constraints ) = @_; + if ( ref $type_name eq 'ARRAY' ) { + @constraints == 0 + || throw_exception( UnionCalledWithAnArrayRefAndAdditionalArgs => array => $type_name, + args => \@constraints + ); + @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 ) = @_; + + Moose::Meta::TypeConstraint::Enum->new( + name => $type_name || '__ANON__', + values => $values, + ); +} + +sub create_duck_type_constraint { + my ( $type_name, $methods ) = @_; + + Moose::Meta::TypeConstraint::DuckType->new( + name => $type_name || '__ANON__', + methods => $methods, + ); +} + +sub match_on_type { + my ($to_match, @cases) = @_; + my $default; + if (@cases % 2 != 0) { + $default = pop @cases; + (ref $default eq 'CODE') + || throw_exception( DefaultToMatchOnTypeMustBeCodeRef => to_match => $to_match, + default_action => $default, + cases_to_be_matched => \@cases + ); + } + while (@cases) { + my ($type, $action) = splice @cases, 0, 2; + + unless (blessed $type && $type->isa('Moose::Meta::TypeConstraint')) { + $type = find_or_parse_type_constraint($type) + || throw_exception( CannotFindTypeGivenToMatchOnType => type => $type, + to_match => $to_match, + action => $action + ); + } + + (ref $action eq 'CODE') + || throw_exception( MatchActionMustBeACodeRef => type_name => $type->name, + action => $action, + to_match => $to_match + ); + + if ($type->check($to_match)) { + local $_ = $to_match; + return $action->($to_match); + } + } + (defined $default) + || throw_exception( NoCasesMatched => to_match => $to_match, + cases_to_be_matched => \@cases + ); + { + local $_ = $to_match; + return $default->($to_match); + } +} + + +## -------------------------------------------------------- +## desugaring functions ... +## -------------------------------------------------------- + +sub _create_type_constraint ($$$;$) { + my $name = shift; + my $parent = shift; + my $check = shift; + my $message = shift; + my $inlined = shift; + + my $pkg_defined_in = scalar( caller(1) ); + + if ( defined $name ) { + my $type = $REGISTRY->get_type_constraint($name); + + ( $type->_package_defined_in eq $pkg_defined_in ) + || throw_exception( TypeConstraintIsAlreadyCreated => package_defined_in => $pkg_defined_in, + type_name => $type->name, + ) + if defined $type; + + if( $name !~ /^[\w:\.]+$/ ) { + throw_exception( InvalidNameForType => name => $name ); + } + } + + my %opts = ( + name => $name, + package_defined_in => $pkg_defined_in, + + ( $check ? ( constraint => $check ) : () ), + ( $message ? ( message => $message ) : () ), + ( $inlined ? ( inlined => $inlined ) : () ), + ); + + my $constraint; + if ( + defined $parent + and $parent + = blessed $parent + ? $parent + : find_or_create_isa_type_constraint($parent) + ) { + $constraint = $parent->create_child_type(%opts); + } + else { + $constraint = Moose::Meta::TypeConstraint->new(%opts); + } + + $REGISTRY->add_type_constraint($constraint) + if defined $name; + + return $constraint; +} + +sub _install_type_coercions ($$) { + my ( $type_name, $coercion_map ) = @_; + my $type = find_type_constraint($type_name); + ( defined $type ) + || throw_exception( CannotFindType => type_name => $type_name ); + + if ( $type->has_coercion ) { + $type->coercion->add_type_coercions(@$coercion_map); + } + else { + my $type_coercion = Moose::Meta::TypeCoercion->new( + type_coercion_map => $coercion_map, + type_constraint => $type + ); + $type->coercion($type_coercion); + } +} + +## -------------------------------------------------------- +## type notation parsing ... +## -------------------------------------------------------- + +{ + + # All I have to say is mugwump++ cause I know + # do not even have enough regexp-fu to be able + # to have written this (I can only barely + # understand it as it is) + # - SL + + use re "eval"; + + my $valid_chars = qr{[\w:\.]}; + my $type_atom = qr{ (?>$valid_chars+) }x; + my $ws = qr{ (?>\s*) }x; + my $op_union = qr{ $ws \| $ws }x; + + my ($type, $type_capture_parts, $type_with_parameter, $union, $any); + if (Class::MOP::IS_RUNNING_ON_5_10) { + my $type_pattern + = q{ (?&type_atom) (?: \[ (?&ws) (?&any) (?&ws) \] )? }; + my $type_capture_parts_pattern + = q{ ((?&type_atom)) (?: \[ (?&ws) ((?&any)) (?&ws) \] )? }; + my $type_with_parameter_pattern + = q{ (?&type_atom) \[ (?&ws) (?&any) (?&ws) \] }; + my $union_pattern + = q{ (?&type) (?> (?: (?&op_union) (?&type) )+ ) }; + my $any_pattern + = q{ (?&type) | (?&union) }; + + my $defines = qr{(?(DEFINE) + (?<valid_chars> $valid_chars) + (?<type_atom> $type_atom) + (?<ws> $ws) + (?<op_union> $op_union) + (?<type> $type_pattern) + (?<type_capture_parts> $type_capture_parts_pattern) + (?<type_with_parameter> $type_with_parameter_pattern) + (?<union> $union_pattern) + (?<any> $any_pattern) + )}x; + + $type = qr{ $type_pattern $defines }x; + $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; + $any = qr{ $any_pattern $defines }x; + } + else { + $type + = qr{ $type_atom (?: \[ $ws (??{$any}) $ws \] )? }x; + $type_capture_parts + = qr{ ($type_atom) (?: \[ $ws ((??{$any})) $ws \] )? }x; + $type_with_parameter + = qr{ $type_atom \[ $ws (??{$any}) $ws \] }x; + $union + = qr{ $type (?> (?: $op_union $type )+ ) }x; + $any + = qr{ $type | $union }x; + } + + + sub _parse_parameterized_type_constraint { + { no warnings 'void'; $any; } # force capture of interpolated lexical + $_[0] =~ m{ $type_capture_parts }x; + return ( $1, $2 ); + } + + sub _detect_parameterized_type_constraint { + { no warnings 'void'; $any; } # force capture of interpolated lexical + $_[0] =~ m{ ^ $type_with_parameter $ }x; + } + + sub _parse_type_constraint_union { + { no warnings 'void'; $any; } # force capture of interpolated lexical + my $given = shift; + my @rv; + while ( $given =~ m{ \G (?: $op_union )? ($type) }gcx ) { + push @rv => $1; + } + ( pos($given) eq length($given) ) + || throw_exception( CouldNotParseType => type => $given, + position => pos($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; + } +} + +## -------------------------------------------------------- +# define some basic built-in types +## -------------------------------------------------------- + +# By making these classes immutable before creating all the types in +# Moose::Util::TypeConstraints::Builtin , we avoid repeatedly calling the slow +# MOP-based accessors. +$_->make_immutable( + inline_constructor => 1, + constructor_name => "_new", + + # these are Class::MOP accessors, so they need inlining + inline_accessors => 1 + ) for grep { $_->is_mutable } + map { Class::MOP::class_of($_) } + qw( + Moose::Meta::TypeConstraint + Moose::Meta::TypeConstraint::Union + Moose::Meta::TypeConstraint::Parameterized + Moose::Meta::TypeConstraint::Parameterizable + Moose::Meta::TypeConstraint::Class + Moose::Meta::TypeConstraint::Role + Moose::Meta::TypeConstraint::Enum + Moose::Meta::TypeConstraint::DuckType + Moose::Meta::TypeConstraint::Registry +); + +require Moose::Util::TypeConstraints::Builtins; +Moose::Util::TypeConstraints::Builtins::define_builtins($REGISTRY); + +my @PARAMETERIZABLE_TYPES + = map { $REGISTRY->get_type_constraint($_) } qw[ScalarRef ArrayRef HashRef Maybe]; + +sub get_all_parameterizable_types {@PARAMETERIZABLE_TYPES} + +sub add_parameterizable_type { + my $type = shift; + ( blessed $type + && $type->isa('Moose::Meta::TypeConstraint::Parameterizable') ) + || throw_exception( AddParameterizableTypeTakesParameterizableType => type_name => $type ); + + push @PARAMETERIZABLE_TYPES => $type; +} + +## -------------------------------------------------------- +# end of built-in types ... +## -------------------------------------------------------- + +{ + my @BUILTINS = list_all_type_constraints(); + sub list_all_builtin_type_constraints {@BUILTINS} +} + +1; + +# ABSTRACT: Type constraint system for Moose + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Moose::Util::TypeConstraints - Type constraint system for Moose + +=head1 VERSION + +version 2.1405 + +=head1 SYNOPSIS + + use Moose::Util::TypeConstraints; + + subtype 'Natural', + as 'Int', + where { $_ > 0 }; + + subtype 'NaturalLessThanTen', + as 'Natural', + where { $_ < 10 }, + message { "This number ($_) is not less than ten!" }; + + coerce 'Num', + from 'Str', + via { 0+$_ }; + + class_type 'DateTimeClass', { class => 'DateTime' }; + + role_type 'Barks', { role => 'Some::Library::Role::Barks' }; + + enum 'RGBColors', [qw(red green blue)]; + + union 'StringOrArray', [qw( String ArrayRef )]; + + no Moose::Util::TypeConstraints; + +=head1 DESCRIPTION + +This module provides Moose with the ability to create custom type +constraints to be used in attribute definition. + +=head2 Important Caveat + +This is B<NOT> a type system for Perl 5. These are type constraints, +and they are not used by Moose unless you tell it to. No type +inference is performed, expressions are not typed, etc. etc. etc. + +A type constraint is at heart a small "check if a value is valid" +function. A constraint can be associated with an attribute. This +simplifies parameter validation, and makes your code clearer to read, +because you can refer to constraints by name. + +=head2 Slightly Less Important Caveat + +It is B<always> a good idea to quote your type names. + +This prevents Perl from trying to execute the call as an indirect +object call. This can be an issue when you have a subtype with the +same name as a valid class. + +For instance: + + subtype DateTime => as Object => where { $_->isa('DateTime') }; + +will I<just work>, while this: + + use DateTime; + subtype DateTime => as Object => where { $_->isa('DateTime') }; + +will fail silently and cause many headaches. The simple way to solve +this, as well as future proof your subtypes from classes which have +yet to have been created, is to quote the type name: + + use DateTime; + subtype 'DateTime', as 'Object', where { $_->isa('DateTime') }; + +=head2 Default Type Constraints + +This module also provides a simple hierarchy for Perl 5 types, here is +that hierarchy represented visually. + + Any + Item + Bool + Maybe[`a] + Undef + Defined + Value + Str + Num + Int + ClassName + RoleName + Ref + ScalarRef[`a] + ArrayRef[`a] + HashRef[`a] + CodeRef + RegexpRef + GlobRef + FileHandle + Object + +B<NOTE:> Any type followed by a type parameter C<[`a]> can be +parameterized, this means you can say: + + ArrayRef[Int] # an array of integers + HashRef[CodeRef] # a hash of str to CODE ref mappings + ScalarRef[Int] # a reference to an integer + Maybe[Str] # value may be a string, may be undefined + +If Moose finds a name in brackets that it does not recognize as an +existing type, it assumes that this is a class name, for example +C<ArrayRef[DateTime]>. + +B<NOTE:> Unless you parameterize a type, then it is invalid to include +the square brackets. I.e. C<ArrayRef[]> will be treated as a new type +name, I<not> as a parameterization of C<ArrayRef>. + +B<NOTE:> The C<Undef> type constraint for the most part works +correctly now, but edge cases may still exist, please use it +sparingly. + +B<NOTE:> The C<ClassName> type constraint does a complex package +existence check. This means that your class B<must> be loaded for this +type constraint to pass. + +B<NOTE:> The C<RoleName> constraint checks a string is a I<package +name> which is a role, like C<'MyApp::Role::Comparable'>. + +=head2 Type Constraint Naming + +Type name declared via this module can only contain alphanumeric +characters, colons (:), and periods (.). + +Since the types created by this module are global, it is suggested +that you namespace your types just as you would namespace your +modules. So instead of creating a I<Color> type for your +B<My::Graphics> module, you would call the type +I<My::Graphics::Types::Color> instead. + +=head2 Use with Other Constraint Modules + +This module can play nicely with other constraint modules with some +slight tweaking. The C<where> clause in types is expected to be a +C<CODE> reference which checks its first argument and returns a +boolean. Since most constraint modules work in a similar way, it +should be simple to adapt them to work with Moose. + +For instance, this is how you could use it with +L<Declare::Constraints::Simple> to declare a completely new type. + + type 'HashOfArrayOfObjects', + where { + IsHashRef( + -keys => HasLength, + -values => IsArrayRef(IsObject) + )->(@_); + }; + +For more examples see the F<t/examples/example_w_DCS.t> test +file. + +Here is an example of using L<Test::Deep> and its non-test +related C<eq_deeply> function. + + type 'ArrayOfHashOfBarsAndRandomNumbers', + where { + eq_deeply($_, + array_each(subhashof({ + bar => isa('Bar'), + random_number => ignore() + }))) + }; + +For a complete example see the +F<t/examples/example_w_TestDeep.t> test file. + +=head2 Error messages + +Type constraints can also specify custom error messages, for when they fail to +validate. This is provided as just another coderef, which receives the invalid +value in C<$_>, as in: + + subtype 'PositiveInt', + as 'Int', + where { $_ > 0 }, + message { "$_ is not a positive integer!" }; + +If no message is specified, a default message will be used, which indicates +which type constraint was being used and what value failed. If +L<Devel::PartialDump> (version 0.14 or higher) is installed, it will be used to +display the invalid value, otherwise it will just be printed as is. + +=head1 FUNCTIONS + +=head2 Type Constraint Constructors + +The following functions are used to create type constraints. They +will also register the type constraints your create in a global +registry that is used to look types up by name. + +See the L</SYNOPSIS> for an example of how to use these. + +=over 4 + +=item B<< subtype 'Name', as 'Parent', where { } ... >> + +This creates a named subtype. + +If you provide a parent that Moose does not recognize, it will +automatically create a new class type constraint for this name. + +When creating a named type, the C<subtype> function should either be +called with the sugar helpers (C<where>, C<message>, etc), or with a +name and a hashref of parameters: + + subtype( 'Foo', { where => ..., message => ... } ); + +The valid hashref keys are C<as> (the parent), C<where>, C<message>, +and C<inline_as>. + +=item B<< subtype as 'Parent', where { } ... >> + +This creates an unnamed subtype and will return the type +constraint meta-object, which will be an instance of +L<Moose::Meta::TypeConstraint>. + +When creating an anonymous type, the C<subtype> function should either +be called with the sugar helpers (C<where>, C<message>, etc), or with +just a hashref of parameters: + + subtype( { where => ..., message => ... } ); + +=item B<class_type ($class, ?$options)> + +Creates a new subtype of C<Object> with the name C<$class> and the +metaclass L<Moose::Meta::TypeConstraint::Class>. + + # Create a type called 'Box' which tests for objects which ->isa('Box') + class_type 'Box'; + +By default, the name of the type and the name of the class are the same, but +you can specify both separately. + + # Create a type called 'Box' which tests for objects which ->isa('ObjectLibrary::Box'); + class_type 'Box', { class => 'ObjectLibrary::Box' }; + +=item B<role_type ($role, ?$options)> + +Creates a C<Role> type constraint with the name C<$role> and the +metaclass L<Moose::Meta::TypeConstraint::Role>. + + # Create a type called 'Walks' which tests for objects which ->does('Walks') + role_type 'Walks'; + +By default, the name of the type and the name of the role are the same, but +you can specify both separately. + + # Create a type called 'Walks' which tests for objects which ->does('MooseX::Role::Walks'); + role_type 'Walks', { role => 'MooseX::Role::Walks' }; + +=item B<maybe_type ($type)> + +Creates a type constraint for either C<undef> or something of the +given type. + +=item B<duck_type ($name, \@methods)> + +This will create a subtype of Object and test to make sure the value +C<can()> do the methods in C<\@methods>. + +This is intended as an easy way to accept non-Moose objects that +provide a certain interface. If you're using Moose classes, we +recommend that you use a C<requires>-only Role instead. + +=item B<duck_type (\@methods)> + +If passed an ARRAY reference as the only parameter instead of the +C<$name>, C<\@methods> pair, this will create an unnamed duck type. +This can be used in an attribute definition like so: + + has 'cache' => ( + is => 'ro', + isa => duck_type( [qw( get_set )] ), + ); + +=item B<enum ($name, \@values)> + +This will create a basic subtype for a given set of strings. +The resulting constraint will be a subtype of C<Str> and +will match any of the items in C<\@values>. It is case sensitive. +See the L</SYNOPSIS> for a simple example. + +B<NOTE:> This is not a true proper enum type, it is simply +a convenient constraint builder. + +=item B<enum (\@values)> + +If passed an ARRAY reference as the only parameter instead of the +C<$name>, C<\@values> pair, this will create an unnamed enum. This +can then be used in an attribute definition like so: + + has 'sort_order' => ( + is => 'ro', + 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. + +It takes a single argument, which is the name of a parent type. + +=item B<where { ... }> + +This is just sugar for the type constraint construction syntax. + +It takes a subroutine reference as an argument. When the type +constraint is tested, the reference is run with the value to be tested +in C<$_>. This reference should return true or false to indicate +whether or not the constraint check passed. + +=item B<message { ... }> + +This is just sugar for the type constraint construction syntax. + +It takes a subroutine reference as an argument. When the type +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. If your parent type constraint defines its own inlining, you can simply use +that to avoid repeating code. For example, here is the inlining code for the +C<Value> type, which is a subtype of C<Defined>: + + sub { + $_[0]->parent()->_inline_check($_[1]) + . ' && !ref(' . $_[1] . ')' + } + +=item B<< type 'Name', where { } ... >> + +This creates a base type, which has no parent. + +The C<type> function should either be called with the sugar helpers +(C<where>, C<message>, etc), or with a name and a hashref of +parameters: + + type( 'Foo', { where => ..., message => ... } ); + +The valid hashref keys are C<where>, C<message>, and C<inlined_as>. + +=back + +=head2 Type Constraint Utilities + +=over 4 + +=item B<< match_on_type $value => ( $type => \&action, ... ?\&default ) >> + +This is a utility function for doing simple type based dispatching similar to +match/case in OCaml and case/of in Haskell. It is not as featureful as those +languages, nor does not it support any kind of automatic destructuring +bind. Here is a simple Perl pretty printer dispatching over the core Moose +types. + + sub ppprint { + my $x = shift; + match_on_type $x => ( + HashRef => sub { + my $hash = shift; + '{ ' + . ( + join ", " => map { $_ . ' => ' . ppprint( $hash->{$_} ) } + sort keys %$hash + ) . ' }'; + }, + ArrayRef => sub { + my $array = shift; + '[ ' . ( join ", " => map { ppprint($_) } @$array ) . ' ]'; + }, + CodeRef => sub {'sub { ... }'}, + RegexpRef => sub { 'qr/' . $_ . '/' }, + GlobRef => sub { '*' . B::svref_2object($_)->NAME }, + Object => sub { $_->can('to_string') ? $_->to_string : $_ }, + ScalarRef => sub { '\\' . ppprint( ${$_} ) }, + Num => sub {$_}, + Str => sub { '"' . $_ . '"' }, + Undef => sub {'undef'}, + => sub { die "I don't know what $_ is" } + ); + } + +Or a simple JSON serializer: + + sub to_json { + my $x = shift; + match_on_type $x => ( + HashRef => sub { + my $hash = shift; + '{ ' + . ( + join ", " => + map { '"' . $_ . '" : ' . to_json( $hash->{$_} ) } + sort keys %$hash + ) . ' }'; + }, + ArrayRef => sub { + my $array = shift; + '[ ' . ( join ", " => map { to_json($_) } @$array ) . ' ]'; + }, + Num => sub {$_}, + Str => sub { '"' . $_ . '"' }, + Undef => sub {'null'}, + => sub { die "$_ is not acceptable json type" } + ); + } + +The matcher is done by mapping a C<$type> to an C<\&action>. The C<$type> can +be either a string type or a L<Moose::Meta::TypeConstraint> object, and +C<\&action> is a subroutine reference. This function will dispatch on the +first match for C<$value>. It is possible to have a catch-all by providing an +additional subroutine reference as the final argument to C<match_on_type>. + +=back + +=head2 Type Coercion Constructors + +You can define coercions for type constraints, which allow you to +automatically transform values to something valid for the type +constraint. If you ask your accessor to coerce, then Moose will run +the type-coercion code first, followed by the type constraint +check. This feature should be used carefully as it is very powerful +and could easily take off a limb if you are not careful. + +See the L</SYNOPSIS> for an example of how to use these. + +=over 4 + +=item B<< coerce 'Name', from 'OtherName', via { ... } >> + +This defines a coercion from one type to another. The C<Name> argument +is the type you are coercing I<to>. + +To define multiple coercions, supply more sets of from/via pairs: + + coerce 'Name', + from 'OtherName', via { ... }, + from 'ThirdName', via { ... }; + +=item B<from 'OtherName'> + +This is just sugar for the type coercion construction syntax. + +It takes a single type name (or type object), which is the type being +coerced I<from>. + +=item B<via { ... }> + +This is just sugar for the type coercion construction syntax. + +It takes a subroutine reference. This reference will be called with +the value to be coerced in C<$_>. It is expected to return a new value +of the proper type for the coercion. + +=back + +=head2 Creating and Finding Type Constraints + +These are additional functions for creating and finding type +constraints. Most of these functions are not available for +importing. The ones that are importable as specified. + +=over 4 + +=item B<find_type_constraint($type_name)> + +This function can be used to locate the L<Moose::Meta::TypeConstraint> +object for a named type. + +This function is importable. + +=item B<register_type_constraint($type_object)> + +This function will register a L<Moose::Meta::TypeConstraint> with the +global type registry. + +This function is importable. + +=item B<normalize_type_constraint_name($type_constraint_name)> + +This method takes a type constraint name and returns the normalized +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_parameterized_type_constraint($type_name)> + +Given a C<$type_name> in the form of C<'BaseType[ContainerType]'>, +this will create a new L<Moose::Meta::TypeConstraint::Parameterized> +object. The C<BaseType> must already exist as a parameterizable +type. + +=item B<create_class_type_constraint($class, $options)> + +Given a class name this function will create a new +L<Moose::Meta::TypeConstraint::Class> object for that class name. + +The C<$options> is a hash reference that will be passed to the +L<Moose::Meta::TypeConstraint::Class> constructor (as a hash). + +=item B<create_role_type_constraint($role, $options)> + +Given a role name this function will create a new +L<Moose::Meta::TypeConstraint::Role> object for that role name. + +The C<$options> is a hash reference that will be passed to the +L<Moose::Meta::TypeConstraint::Role> constructor (as a hash). + +=item B<create_enum_type_constraint($name, $values)> + +Given a enum name this function will create a new +L<Moose::Meta::TypeConstraint::Enum> object for that enum name. + +=item B<create_duck_type_constraint($name, $methods)> + +Given a duck type name this function will create a new +L<Moose::Meta::TypeConstraint::DuckType> object for that enum name. + +=item B<find_or_parse_type_constraint($type_name)> + +Given a type name, this first attempts to find a matching constraint +in the global registry. + +If the type name is a union or parameterized type, it will create a +new object of the appropriate, but if given a "regular" type that does +not yet exist, it simply returns false. + +When given a union or parameterized type, the member or base type must +already exist. + +If it creates a new union or parameterized type, it will add it to the +global registry. + +=item B<find_or_create_isa_type_constraint($type_name)> + +=item B<find_or_create_does_type_constraint($type_name)> + +These functions will first call C<find_or_parse_type_constraint>. If +that function does not return a type, a new type object will +be created. + +The C<isa> variant will use C<create_class_type_constraint> and the +C<does> variant will use C<create_role_type_constraint>. + +=item B<get_type_constraint_registry> + +Returns the L<Moose::Meta::TypeConstraint::Registry> object which +keeps track of all type constraints. + +=item B<list_all_type_constraints> + +This will return a list of type constraint names in the global +registry. You can then fetch the actual type object using +C<find_type_constraint($type_name)>. + +=item B<list_all_builtin_type_constraints> + +This will return a list of builtin type constraints, meaning those +which are defined in this module. See the L<Default Type Constraints> +section for a complete list. + +=item B<export_type_constraints_as_functions> + +This will export all the current type constraints as functions into +the caller's namespace (C<Int()>, C<Str()>, etc). Right now, this is +mostly used for testing, but it might prove useful to others. + +=item B<get_all_parameterizable_types> + +This returns all the parameterizable types that have been registered, +as a list of type objects. + +=item B<add_parameterizable_type($type)> + +Adds C<$type> to the list of parameterizable types + +=back + +=head1 BUGS + +See L<Moose/BUGS> for details on reporting bugs. + +=head1 AUTHORS + +=over 4 + +=item * + +Stevan Little <stevan.little@iinteractive.com> + +=item * + +Dave Rolsky <autarch@urth.org> + +=item * + +Jesse Luehrs <doy@tozt.net> + +=item * + +Shawn M Moore <code@sartak.org> + +=item * + +יובל קוג'מן (Yuval Kogman) <nothingmuch@woobling.org> + +=item * + +Karen Etheridge <ether@cpan.org> + +=item * + +Florian Ragwitz <rafl@debian.org> + +=item * + +Hans Dieter Pearcey <hdp@weftsoar.net> + +=item * + +Chris Prather <chris@prather.org> + +=item * + +Matt S Trout <mst@shadowcat.co.uk> + +=back + +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2006 by Infinity Interactive, Inc.. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut diff --git a/lib/Moose/Util/TypeConstraints/Builtins.pm b/lib/Moose/Util/TypeConstraints/Builtins.pm new file mode 100644 index 0000000..400afe6 --- /dev/null +++ b/lib/Moose/Util/TypeConstraints/Builtins.pm @@ -0,0 +1,305 @@ +package Moose::Util::TypeConstraints::Builtins; +our $VERSION = '2.1405'; + +use strict; +use warnings; + +use Class::Load qw( is_class_loaded ); +use List::Util 1.33 (); +use Scalar::Util qw( blessed ); + +sub type { goto &Moose::Util::TypeConstraints::type } +sub subtype { goto &Moose::Util::TypeConstraints::subtype } +sub as { goto &Moose::Util::TypeConstraints::as } +sub where (&) { goto &Moose::Util::TypeConstraints::where } +sub inline_as (&) { goto &Moose::Util::TypeConstraints::inline_as } + +sub define_builtins { + my $registry = shift; + + type 'Any' # meta-type including all + => where {1} + => inline_as { '1' }; + + subtype 'Item' # base type + => as 'Any' + => inline_as { '1' }; + + subtype 'Undef' + => as 'Item' + => where { !defined($_) } + => inline_as { + '!defined(' . $_[1] . ')' + }; + + subtype 'Defined' + => as 'Item' + => where { defined($_) } + => inline_as { + 'defined(' . $_[1] . ')' + }; + + subtype 'Bool' + => as 'Item' + => where { !defined($_) || $_ eq "" || "$_" eq '1' || "$_" eq '0' } + => inline_as { + '(' + . '!defined(' . $_[1] . ') ' + . '|| ' . $_[1] . ' eq "" ' + . '|| (' . $_[1] . '."") eq "1" ' + . '|| (' . $_[1] . '."") eq "0"' + . ')' + }; + + subtype 'Value' + => as 'Defined' + => where { !ref($_) } + => inline_as { + $_[0]->parent()->_inline_check($_[1]) + . ' && !ref(' . $_[1] . ')' + }; + + subtype 'Ref' + => as 'Defined' + => where { ref($_) } + # no need to call parent - ref also checks for definedness + => inline_as { 'ref(' . $_[1] . ')' }; + + subtype 'Str' + => as 'Value' + => where { ref(\$_) eq 'SCALAR' || ref(\(my $val = $_)) eq 'SCALAR' } + => inline_as { + $_[0]->parent()->_inline_check($_[1]) + . ' && (' + . 'ref(\\' . $_[1] . ') eq "SCALAR"' + . ' || ref(\\(my $val = ' . $_[1] . ')) eq "SCALAR"' + . ')' + }; + + my $value_type = Moose::Util::TypeConstraints::find_type_constraint('Value'); + subtype 'Num' + => as 'Str' + => where { + my $val = $_; + ($val =~ /\A[+-]?[0-9]+\z/) || + ( $val =~ /\A(?:[+-]?) #matches optional +- in the beginning + (?=[0-9]|\.[0-9]) #matches previous +- only if there is something like 3 or .3 + [0-9]* #matches 0-9 zero or more times + (?:\.[0-9]+)? #matches optional .89 or nothing + (?:[Ee](?:[+-]?[0-9]+))? #matches E1 or e1 or e-1 or e+1 etc + \z/x ); + } + => inline_as { + # the long Str tests are redundant here + #storing $_[1] in a temporary value, + #so that $_[1] won't get converted to a string for regex match + #see t/attributes/numeric_defaults.t for more details + 'my $val = '.$_[1].';'. + $value_type->_inline_check('$val') + .' && ( $val =~ /\A[+-]?[0-9]+\z/ || ' + . '$val =~ /\A(?:[+-]?) #matches optional +- in the beginning + (?=[0-9]|\.[0-9]) #matches previous +- only if there is something like 3 or .3 + [0-9]* #matches 0-9 zero or more times + (?:\.[0-9]+)? #matches optional .89 or nothing + (?:[Ee](?:[+-]?[0-9]+))? #matches E1 or e1 or e-1 or e+1 etc + \z/x ); ' + }; + + subtype 'Int' + => as 'Num' + => where { (my $val = $_) =~ /\A-?[0-9]+\z/ } + => inline_as { + $value_type->_inline_check($_[1]) + . ' && (my $val = ' . $_[1] . ') =~ /\A-?[0-9]+\z/' + }; + + subtype 'CodeRef' + => as 'Ref' + => where { ref($_) eq 'CODE' } + => inline_as { 'ref(' . $_[1] . ') eq "CODE"' }; + + subtype 'RegexpRef' + => as 'Ref' + => where( \&_RegexpRef ) + => inline_as { + 'Moose::Util::TypeConstraints::Builtins::_RegexpRef(' . $_[1] . ')' + }; + + subtype 'GlobRef' + => as 'Ref' + => where { ref($_) eq 'GLOB' } + => inline_as { 'ref(' . $_[1] . ') eq "GLOB"' }; + + # NOTE: scalar filehandles are GLOB refs, but a GLOB ref is not always a + # filehandle + subtype 'FileHandle' + => as 'Ref' + => where { + (ref($_) eq "GLOB" && Scalar::Util::openhandle($_)) + || (blessed($_) && $_->isa("IO::Handle")); + } + => inline_as { + '(ref(' . $_[1] . ') eq "GLOB" ' + . '&& Scalar::Util::openhandle(' . $_[1] . ')) ' + . '|| (Scalar::Util::blessed(' . $_[1] . ') ' + . '&& ' . $_[1] . '->isa("IO::Handle"))' + }; + + subtype 'Object' + => as 'Ref' + => where { blessed($_) } + => inline_as { 'Scalar::Util::blessed(' . $_[1] . ')' }; + + subtype 'ClassName' + => as 'Str' + => where { is_class_loaded($_) } + # the long Str tests are redundant here + => inline_as { 'Class::Load::is_class_loaded(' . $_[1] . ')' }; + + subtype 'RoleName' + => as 'ClassName' + => where { + (Class::MOP::class_of($_) || return)->isa('Moose::Meta::Role'); + } + => inline_as { + $_[0]->parent()->_inline_check($_[1]) + . ' && do {' + . 'my $meta = Class::MOP::class_of(' . $_[1] . ');' + . '$meta && $meta->isa("Moose::Meta::Role");' + . '}' + }; + + $registry->add_type_constraint( + Moose::Meta::TypeConstraint::Parameterizable->new( + name => 'ScalarRef', + package_defined_in => __PACKAGE__, + parent => + Moose::Util::TypeConstraints::find_type_constraint('Ref'), + constraint => sub { ref($_) eq 'SCALAR' || ref($_) eq 'REF' }, + constraint_generator => sub { + my $type_parameter = shift; + my $check = $type_parameter->_compiled_type_constraint; + return sub { + return $check->( ${$_} ); + }; + }, + inlined => sub { + 'ref(' . $_[1] . ') eq "SCALAR" ' + . '|| ref(' . $_[1] . ') eq "REF"' + }, + inline_generator => sub { + my $self = shift; + my $type_parameter = shift; + my $val = shift; + '(ref(' . $val . ') eq "SCALAR" || ref(' . $val . ') eq "REF") ' + . '&& ' . $type_parameter->_inline_check('${(' . $val . ')}') + }, + ) + ); + + $registry->add_type_constraint( + Moose::Meta::TypeConstraint::Parameterizable->new( + name => 'ArrayRef', + package_defined_in => __PACKAGE__, + parent => + Moose::Util::TypeConstraints::find_type_constraint('Ref'), + constraint => sub { ref($_) eq 'ARRAY' }, + constraint_generator => sub { + my $type_parameter = shift; + my $check = $type_parameter->_compiled_type_constraint; + return sub { + foreach my $x (@$_) { + ( $check->($x) ) || return; + } + 1; + } + }, + inlined => sub { 'ref(' . $_[1] . ') eq "ARRAY"' }, + inline_generator => sub { + my $self = shift; + my $type_parameter = shift; + my $val = shift; + + 'do {' + . 'my $check = ' . $val . ';' + . 'ref($check) eq "ARRAY" ' + . '&& &List::Util::all(' + . 'sub { ' . $type_parameter->_inline_check('$_') . ' }, ' + . '@{$check}' + . ')' + . '}'; + }, + ) + ); + + $registry->add_type_constraint( + Moose::Meta::TypeConstraint::Parameterizable->new( + name => 'HashRef', + package_defined_in => __PACKAGE__, + parent => + Moose::Util::TypeConstraints::find_type_constraint('Ref'), + constraint => sub { ref($_) eq 'HASH' }, + constraint_generator => sub { + my $type_parameter = shift; + my $check = $type_parameter->_compiled_type_constraint; + return sub { + foreach my $x ( values %$_ ) { + ( $check->($x) ) || return; + } + 1; + } + }, + inlined => sub { 'ref(' . $_[1] . ') eq "HASH"' }, + inline_generator => sub { + my $self = shift; + my $type_parameter = shift; + my $val = shift; + + 'do {' + . 'my $check = ' . $val . ';' + . 'ref($check) eq "HASH" ' + . '&& &List::Util::all(' + . 'sub { ' . $type_parameter->_inline_check('$_') . ' }, ' + . 'values %{$check}' + . ')' + . '}'; + }, + ) + ); + + $registry->add_type_constraint( + Moose::Meta::TypeConstraint::Parameterizable->new( + name => 'Maybe', + package_defined_in => __PACKAGE__, + parent => + Moose::Util::TypeConstraints::find_type_constraint('Item'), + constraint => sub {1}, + constraint_generator => sub { + my $type_parameter = shift; + my $check = $type_parameter->_compiled_type_constraint; + return sub { + return 1 if not( defined($_) ) || $check->($_); + return; + } + }, + inlined => sub {'1'}, + inline_generator => sub { + my $self = shift; + my $type_parameter = shift; + my $val = shift; + '!defined(' . $val . ') ' + . '|| (' . $type_parameter->_inline_check($val) . ')' + }, + ) + ); +} + +1; + +__END__ + +=pod + +=for pod_coverage_needs_some_pod + +=cut |