Skip Menu |

This queue is for tickets about the Moose CPAN distribution.

Report information
The Basics
Id: 46347
Status: open
Priority: 0/
Queue: Moose

People
Owner: Nobody in particular
Requestors: kane [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: Important
Broken in: 0.79
Fixed in: (no value)



Subject: Roles + 'requires attribute' doesn't work
If a role says 'requires attribute', Moose will throw an exception, even if the attribute is provided and the 'with' clause specifies the right order. See the following test script: $ find . -type f | xargs perl -le'print "#### $_ ####" and print `cat $_` for @ARGV' ; #### ./y.pl #### use strict; use warnings; use lib 'lib'; use Foo; Foo->new; #### ./lib/RoleB.pm #### package RoleB; use Moose::Role; requires 'role_a'; 1; #### ./lib/Foo.pm #### package Foo; use Moose; with 'RoleA', 'RoleB'; no Moose; __PACKAGE__->meta->make_immutable; 1; #### ./lib/RoleA.pm #### package RoleA; use Moose::Role; has 'role_a' => ( is => 'rw' ); 1; $ perl y.pl 'RoleA|RoleB' requires the method 'role_a' to be implemented by 'Foo' at /home/demo/perl/5.10/lib/site_perl/5.10.0/Moose/Meta/Role/Application.pm line 59 Moose::Meta::Role::Application::apply('Moose::Meta::Role::Application::ToClass=HASH(0x89e4be0)', 'Moose::Meta::Role::Composite=HASH(0x89e4dc0)', 'Moose::Meta::Class=HASH(0x858c790)') called at /home/demo/perl/5.10/lib/site_perl/5.10.0/Moose/Meta/Role/Application/ToClass.pm line 18 Moose::Meta::Role::Application::ToClass::apply('Moose::Meta::Role::Application::ToClass=HASH(0x89e4be0)', 'Moose::Meta::Role::Composite=HASH(0x89e4dc0)', 'Moose::Meta::Class=HASH(0x858c790)') called at /home/demo/perl/5.10/lib/site_perl/5.10.0/Moose/Meta/Role.pm line 441 Moose::Meta::Role::apply('Moose::Meta::Role::Composite=HASH(0x89e4dc0)', 'Moose::Meta::Class=HASH(0x858c790)') called at /home/demo/perl/5.10/lib/site_perl/5.10.0/Moose/Util.pm line 119 Moose::Util::_apply_all_roles('Moose::Meta::Class=HASH(0x858c790)', 'CODE(0x88745e8)', 'RoleA', 'RoleB') called at /home/demo/perl/5.10/lib/site_perl/5.10.0/Moose/Util.pm line 82 Moose::Util::apply_all_roles('Moose::Meta::Class=HASH(0x858c790)', 'RoleA', 'RoleB') called at /home/demo/perl/5.10/lib/site_perl/5.10.0/Moose.pm line 65 Moose::with('Foo', 'RoleA', 'RoleB') called at /home/demo/perl/5.10/lib/site_perl/5.10.0/Moose/Exporter.pm line 201 Moose::with('RoleA', 'RoleB') called at lib/Foo.pm line 3 require Foo.pm called at y.pl line 4 main::BEGIN() called at lib/Foo.pm line 0 eval {...} called at lib/Foo.pm line 0 Compilation failed in require at y.pl line 4. BEGIN failed--compilation aborted at y.pl line 4.
Subject: Role requires() fails to be satisfied by attributes for some compositions
From: Vince Veselosky <vince [...] control-escape.com>
I've run into this bug as well. I have attached a test file that feels out the edges to clarify the issue. The test contains 14 cases, 3 of which fail (using Moose 0.89, Class::MOP 0.92, perl 5.8.8). Basically, attributes declared in a Role do not satisfy the requires() of another role, except in the case where the two roles are composed into the same class with separate with() statements in the proper order. Composing in a single with() statement (regardless of order), or consuming one role from another, results in failure. I hope this test makes it easier to track down the cause. It's a little over my head at the moment. :)
#!/usr/bin/perl use strict; use Test::More tests =>14; use Test::Exception; { package My::Role1; use Moose::Role; requires 'test_output'; } { package My::Role2; use Moose::Role; has test_output => (is => 'rw'); with 'My::Role1'; } { package My::Role3; use Moose::Role; sub test_output {} with 'My::Role1'; } { package My::Role4; use Moose::Role; has test_output => (is => 'rw'); } { package My::Role5; use Moose::Role; sub test_output {} } { package My::Base1; use Moose; has test_output => (is => 'rw'); } { package My::Base2; use Moose; sub test_output {} } # Roles providing attributes/methods should satisfy requires() of other # roles they consume. lives_ok { package My::Test1; use Moose; with 'My::Role2'; } 'role2(provides attribute) consumes role1'; lives_ok { package My::Test2; use Moose; with 'My::Role3'; } 'role3(provides method) consumes role1'; # As I understand the design, Roles composed in the same with() statement # should NOT demonstrate ordering dependency. Alter these tests if that # assumption is false. -Vince Veselosky lives_ok { package My::Test3; use Moose; with 'My::Role4','My::Role1'; } 'class consumes role4(provides attribute), role1'; lives_ok { package My::Test4; use Moose; with 'My::Role1','My::Role4'; } 'class consumes role1, role4(provides attribute)'; lives_ok { package My::Test5; use Moose; with 'My::Role5','My::Role1'; } 'class consumes role5(provides method), role1'; lives_ok { package My::Test6; use Moose; with 'My::Role1','My::Role5'; } 'class consumes role1, role5(provides method)'; # Inherited methods/attributes should satisfy requires(), as long as # extends() comes first in code order. lives_ok {package My::Test7; use Moose; extends 'My::Base1'; with 'My::Role1';} 'class extends base1(provides attribute), consumes role1'; lives_ok {package My::Test8; use Moose; extends 'My::Base2'; with 'My::Role1';} 'class extends base2(provides method), consumes role1'; # Attributes/methods implemented in class should satisfy requires() lives_ok {package My::Test9; use Moose; has 'test_output',is=>'rw'; with 'My::Role1';} 'class provides attribute, consumes role1'; lives_ok {package My::Test10; use Moose; sub test_output{} with 'My::Role1'; } 'class provides method, consumes role1'; # Roles composed in separate with() statements SHOULD demonstrate ordering # dependency. See comment with tests 3-6 above. lives_ok { package My::Test11; use Moose; with 'My::Role4'; with 'My::Role1';} 'class consumes role4(provides attribute); consumes role1'; dies_ok { package My::Test12; use Moose; with 'My::Role1'; with 'My::Role4';} 'class consumes role1; consumes role4(provides attribute)'; lives_ok { package My::Test13; use Moose; with 'My::Role5'; with 'My::Role1';} 'class consumes role5(provides method); consumes role1'; dies_ok { package My::Test14; use Moose; with 'My::Role1'; with 'My::Role5'; } 'class consumes role1; consumes role5(provides method)';
On Sun Aug 23 11:34:12 2009, http://veselosky.myopenid.com/ wrote: Show quoted text
> I've run into this bug as well. I have attached a test file that feels > out the edges to clarify the issue. The test contains 14 cases, 3 of > which fail (using Moose 0.89, Class::MOP 0.92, perl 5.8.8). > > Basically, attributes declared in a Role do not satisfy the requires() > of another role, except in the case where the two roles are composed > into the same class with separate with() statements in the proper order. > Composing in a single with() statement (regardless of order), or > consuming one role from another, results in failure. > > I hope this test makes it easier to track down the cause. It's a little > over my head at the moment. :)
This is mostly an artifact of the not-so-great implementation of roles. Plans have long been brewing to fix this, but it's a bug job, and no one's been sucker enough to start it yet.
Still broke, just wrote a test case thinking one hadn't been done... package Role; use Moose::Role; requires 'attr'; package main; use Test::More tests => 1; eval { package Class; use Moose; with 'Role'; has 'attr' => ( isa => 'ro', required => 1 ); }; unlike ( $@, qr/requires the method .*? to be implemented by/, 'requires sufficed with attribute' ); -- Evan Carroll perl@evancarroll.com http://www.evancarroll.com
On Mon Nov 02 18:35:51 2009, ECARROLL wrote: Show quoted text
> Still broke, just wrote a test case thinking one hadn't been done...
All this test case demonstrates is that attributes don't install methods before you call has() to create them (surprise). Vince, I've added your test file to the repo, with the failing tests marked TODO. Thanks. Dave did a bunch of work on role attributes in 0.93_01, so the path to fixing this should be smoother now (I hope).
Show quoted text
> Dave did a bunch of work on role attributes in 0.93_01, so the path to > fixing this should be smoother now (I hope).
Good luck, again. -- Evan Carroll System Lord of the Internets http://www.evancarroll.com
I can submit a patch with B::EndOfScope if it would be permitted? That would be an easy to compose roles only after the base class has compiled. -- Evan Carroll System Lord of the Internets http://www.evancarroll.com
I have experienced this issue myself, I have hacked my way around this previously by predeclaring such methods as such: Show quoted text
> package RoleA; > > use Moose; > > sub 'attribute_a'; > sub 'delegated_a'; > > has attribute_a => (is => 'rw', handles => ['delegated_a']);
Show quoted text
> package RoleB; > > use Moose::Role; > > requires 'attribute_a', 'delegated_a';
Show quoted text
> package Consumer; > > use Moose; > with qw/RoleA RoleB/;
Today I ran into the same bug but not between two roles, but between a role and a class - specifically trying to require a delegated method. Trying to predeclare the sub causes (sensibly enough) an error, but this lead to the situation where a class which provides a method (via delagation) is accused by roles it consumes of not providing that method. I could move it out to a role and use the pre-declaration trick, but, well, it's just all a bit icky. I guess the end of scope solution proposed above would sort this out: is any progress being made on that?
My patch for RT #77406 seems to solve this too.