Stephen Quinney <step...@jadevine.org.uk> wrote: >Stephen Quinney <step...@jadevine.org.uk> wrote: >> { >> package SJQ::Role::Foo; >> use Moo::Role; >> use MooX::HandlesVia; >> } >> >> { >> package SJQ::Role::Bar; >> use Moo::Role; >> use MooX::HandlesVia; >> } >> >> { >> package SJQ::Baz; >> use Moo; >> >> with 'SJQ::Role::Foo','SJQ::Role::Bar'; >> >> use namespace::clean; >> }
Hi, Stephen. What's happening here is that MooX::HandlesVia looks for the "has" routine in the package importing it (normally the one provided by Moo or Moo::Role), wraps it with a version that understands "handles_via", and replaces "has" in the importing package with the wrapped version. Then Moo::Role treats the wrapped "has" as a method implemented by the role, rather than as a declarator to be ignored; so the wrapped "has" implementations in each role are treated as conflicting methods, with the conflict to be resolved by the class that composes them. I think this behaviour is as documented: https://metacpan.org/pod/Moo::Role#CLEANING-UP-IMPORTS "Moo::Role cleans up its own imported methods and any imports declared before the use Moo::Role statement automatically. Anything imported after use Moo::Role will be composed into consuming packages." That is: since the wrapped "has" in each role isn't one of Moo::Role's own imported methods, and it's (necessarily) added after Moo::Role is imported, and it isn't cleaned up in any other way, Moo::Role is behaving as documented. I suspect the simplest workaround is to add "use namespace::clean" to each role; this will remove the wrapped "has" routines. > To partially answer my own question, it appears that if I use "with" once > for each role it works fine, for example: > > { > package SJQ::Baz; > use Moo; > > with 'SJQ::Role::Foo'; > with 'SJQ::Role::Bar'; > > use namespace::clean; > } That means something slightly different, which is why it doesn't yield the exception you've seen. Using one "with" for several roles composes the roles simultaneously-ish, specifically so that method conflicts are detected. Using multiple "with" calls composes them serially, and therefore does conflict detection serially, too. So the "has" in your ::Foo gets installed as a method in ::Baz first; then the "has" in ::Bar is ignored, because Foo's "has" is treated as a method that the class has implemented (which is one way to resolve a genuine role method conflict). A good rule of thumb is to use serial role composition only when you know exactly why you're doing that (which is normally going to be to resolve a method conflict in favour of the implementation provided by one of the roles). In this case, my guess is that you're going to be better off fixing the spurious conflict, rather than papering over it. -- Aaron Crane ** http://aaroncrane.co.uk/